JAL-2349 allow PAE or other contact matrices to hold a coordinate mapping allowing...
[jalview.git] / src / jalview / renderer / ContactMapRenderer.java
1 /**
2  * 
3  */
4 package jalview.renderer;
5
6 import java.awt.Color;
7 import java.awt.Graphics;
8 import java.util.Iterator;
9
10 import jalview.api.AlignViewportI;
11 import jalview.datamodel.AlignmentAnnotation;
12 import jalview.datamodel.Annotation;
13 import jalview.datamodel.ColumnSelection;
14 import jalview.datamodel.ContactListI;
15 import jalview.datamodel.ContactMatrixI;
16 import jalview.datamodel.ContactRange;
17 import jalview.datamodel.HiddenColumns;
18 import jalview.renderer.api.AnnotationRowRendererI;
19
20 /**
21  * @author jprocter
22  *
23  */
24 public abstract class ContactMapRenderer implements AnnotationRowRendererI
25 {
26   /**
27    * bean holding colours for shading
28    * @author jprocter
29    *
30    */
31   public class Shading
32   {
33     /**
34      * shown when no data available from map
35      */
36     Color no_data;
37     /**
38      * shown for region not currently visible - should normally not see this
39      */
40     Color hidden;
41     /**
42      * linear shading scheme min/max
43      */
44     Color maxColor, minColor;
45
46     /**
47      * linear shading scheme min/max for selected region
48      */
49     Color selMinColor, selMaxColor;
50
51     public Shading(Color no_data, Color hidden, Color maxColor,
52             Color minColor, Color selMinColor, Color selMaxColor)
53     {
54       super();
55       this.no_data = no_data;
56       this.hidden = hidden;
57       this.maxColor = maxColor;
58       this.minColor = minColor;
59       this.selMinColor = selMinColor;
60       this.selMaxColor = selMaxColor;
61     }
62
63   }
64
65   final Shading shade;
66
67   /**
68    * build an EBI-AlphaFold style renderer of PAE matrices
69    * @return
70    */
71   public static ContactMapRenderer newPAERenderer()
72   {
73     return new ContactMapRenderer()
74     {
75       @Override
76       public Shading getShade()
77       {
78         return new Shading(Color.pink, Color.red,
79
80                 new Color(246, 252, 243), new Color(0, 60, 26),
81                 new Color(26, 0, 60), new Color(243, 246, 252));
82       }
83     };
84   }
85
86   /**
87    * 
88    * @return instance of Shading used to initialise the renderer
89    */
90   public abstract Shading getShade();
91
92   public ContactMapRenderer()
93   {
94     this.shade = getShade();
95   }
96
97   @Override
98   public void renderRow(Graphics g, int charWidth, int charHeight,
99           boolean hasHiddenColumns, AlignViewportI viewport,
100           HiddenColumns hiddenColumns, ColumnSelection columnSelection,
101           AlignmentAnnotation _aa, Annotation[] aa_annotations, int sRes,
102           int eRes, float min, float max, int y)
103   {
104     if (sRes > aa_annotations.length)
105     {
106       return;
107     }
108     eRes = Math.min(eRes, aa_annotations.length);
109
110     int x = 0, y2 = y;
111
112     g.setColor(shade.no_data);
113
114     g.drawLine(x, y2, (eRes - sRes) * charWidth, y2);
115
116     int column;
117     int aaMax = aa_annotations.length - 1;
118     ContactMatrixI cm = viewport.getContactMatrix(_aa);
119     while (x < eRes - sRes)
120     {
121       column = sRes + x;
122       if (hasHiddenColumns)
123       {
124         column = hiddenColumns.visibleToAbsoluteColumn(column);
125       }
126       // TODO: highlight columns selected
127       boolean colsel = false;
128       if (columnSelection != null)
129       {
130         colsel = columnSelection.contains(column);
131       }
132
133       if (column > aaMax)
134       {
135         break;
136       }
137
138       if (aa_annotations[column] == null)
139       {
140         x++;
141         continue;
142       }
143       ContactListI contacts = viewport.getContactList(_aa, column);
144       if (contacts == null)
145       {
146         x++;
147         continue;
148       }
149       // ContactListI from viewport can map column -> group
150       Color gpcol = (cm==null) ? Color.white: contacts.getColourForGroup(); // cm.getColourForGroup(cm.getGroupsFor(column));
151       
152       // feature still in development - highlight or omit regions hidden in
153       // the alignment - currently marks them as red rows
154       boolean maskHiddenCols = false;
155       // TODO: optionally pass visible column mask to the ContactGeometry object so it maps
156       // only visible contacts to geometry
157       // Bean holding mapping from contact list to pixels
158       // TODO: allow bracketing/limiting of range on contacts to render (like visible column mask but more flexible?)
159       
160       // COntactListI provides mapping for column -> cm-groupmapping
161       final ContactGeometry cgeom = new ContactGeometry(contacts,
162               _aa.graphHeight);
163
164       for (int ht = y2, eht = y2
165               - _aa.graphHeight; ht >= eht; ht -= cgeom.pixels_step)
166       {
167         
168         ContactGeometry.contactInterval ci = cgeom.mapFor(y2 - ht,
169                 y2 - ht + cgeom.pixels_step);
170         // cstart = (int) Math.floor(((double) y2 - ht) * contacts_per_pixel);
171         // cend = (int) Math.min(contact_height,
172         // Math.ceil(cstart + contacts_per_pixel * pixels_step));
173
174         Color col;
175         boolean rowsel = false, containsHidden = false;
176         if (columnSelection != null)
177         {
178           rowsel = cgeom.intersects(ci, columnSelection, hiddenColumns, maskHiddenCols);
179         }
180         // TODO: show selected region
181         if (colsel || rowsel)
182         {
183
184           col = getSelectedColorForRange(min, max, contacts, ci.cStart,
185                   ci.cEnd);
186           if (colsel && rowsel)
187           {
188             col = new Color(col.getBlue(), col.getGreen(), col.getRed());
189           }
190           else
191           {
192             col = new Color(col.getBlue(), col.getBlue(), col.getBlue());
193           }
194         }
195         else
196         {
197           col = getColorForRange(min, max, contacts, ci.cStart, ci.cEnd);
198         }
199         if (containsHidden)
200         {
201           col = shade.hidden;
202         }
203         if (gpcol!=null && gpcol!=Color.white) {
204           // todo - could overlay group as a transparent rectangle ?
205           col = new Color((int)(((float)(col.getRed()+gpcol.getRed()))/2f),
206                   (int)(((float)(col.getGreen()+gpcol.getGreen()))/2f),
207                   (int)(((float)(col.getBlue()+gpcol.getBlue()))/2f));
208         }
209         g.setColor(col);
210         
211         if (cgeom.pixels_step > 1)
212         {
213           g.fillRect(x * charWidth, ht, charWidth, 1 + cgeom.pixels_step);
214         }
215         else
216         {
217           g.drawLine(x * charWidth, ht, (x + 1) * charWidth, ht);
218         }
219       }
220       x++;
221     }
222
223   }
224
225   Color shadeFor(float min, float max, float value)
226   {
227     return jalview.util.ColorUtils.getGraduatedColour(value, 0, shade.minColor,
228             max, shade.maxColor);
229   }
230
231   public Color getColorForRange(float min, float max, ContactListI cl,
232           int i, int j)
233   {
234     ContactRange cr = cl.getRangeFor(i, j);
235     // average for moment - probably more interested in maxIntProj though
236     return shadeFor(min, max, (float) cr.getMean());
237   }
238
239   public Color getSelectedColorForRange(float min, float max,
240           ContactListI cl, int i, int j)
241   {
242     ContactRange cr = cl.getRangeFor(i, j);
243     // average for moment - probably more interested in maxIntProj though
244     return jalview.util.ColorUtils.getGraduatedColour((float) cr.getMean(),
245             0, shade.selMinColor, max, shade.selMaxColor);
246   }
247
248 }