Merge remote-tracking branch 'origin/develop' into
[jalview.git] / src / jalview / gui / OverviewCanvas.java
1 package jalview.gui;
2
3 import jalview.api.AlignViewportI;
4 import jalview.datamodel.SequenceI;
5 import jalview.renderer.AnnotationRenderer;
6 import jalview.renderer.seqfeatures.FeatureColourFinder;
7 import jalview.viewmodel.OverviewDimensions;
8
9 import java.awt.Color;
10 import java.awt.Dimension;
11 import java.awt.Graphics;
12 import java.awt.image.BufferedImage;
13
14 import javax.swing.JComponent;
15
16 public class OverviewCanvas extends JComponent
17 {
18   private static final Color TRANS_GREY = new Color(100, 100, 100, 25);
19
20   private BufferedImage miniMe;
21
22   private BufferedImage lastMiniMe = null;
23
24   public boolean updating = false;
25
26   // This is set true if the user resizes whilst
27   // the overview is being calculated
28   public volatile boolean updateAgain = false;
29
30   // Can set different properties in this seqCanvas than
31   // main visible SeqCanvas
32   private SequenceRenderer sr;
33
34   private jalview.renderer.seqfeatures.FeatureRenderer fr;
35
36   private final AnnotationRenderer renderer = new AnnotationRenderer();
37
38   OverviewDimensions od;
39
40   OverviewPanel op;
41
42   AlignViewport av;
43
44   AlignmentPanel ap;
45
46   public OverviewCanvas(OverviewDimensions overviewDims,
47           AlignViewportI alignvp, AlignmentPanel alignp, OverviewPanel overp)
48   {
49     od = overviewDims;
50     av = alignp.av;
51     ap = alignp;
52     op = overp;
53
54     sr = new SequenceRenderer(av);
55     sr.renderGaps = false;
56     sr.forOverview = true;
57     fr = new FeatureRenderer(ap);
58   }
59
60   public void draw(boolean showSequenceFeatures, boolean showAnnotation)
61   {
62     miniMe = null;
63
64     if (showSequenceFeatures)
65     {
66       fr.transferSettings(ap.getSeqPanel().seqCanvas.getFeatureRenderer());
67     }
68
69     // why do we need to set preferred size again? was set in
70     // updateOverviewImage
71     setPreferredSize(new Dimension(od.getWidth(), od.getHeight()));
72
73     miniMe = new BufferedImage(od.getWidth(), od.getHeight(),
74             BufferedImage.TYPE_INT_RGB);
75
76     Graphics mg = miniMe.getGraphics();
77     mg.setColor(Color.orange);
78     mg.fillRect(0, 0, od.getWidth(), miniMe.getHeight());
79
80     // calculate sampleCol and sampleRow
81     // alignment width is max number of residues/bases
82     // alignment height is number of sequences
83     int alwidth = av.getAlignment().getWidth();
84     int alheight = av.getAlignment().getAbsoluteHeight();
85
86     // sampleCol or sampleRow is the width/height allocated to each residue
87     // in particular, sometimes we may need more than one row/col of the
88     // BufferedImage allocated
89     // sampleCol is how much of a residue to assign to each pixel
90     // sampleRow is how many sequences to assign to each pixel
91     float sampleCol = alwidth / (float) od.getWidth();
92     float sampleRow = alheight / (float) od.getSequencesHeight();
93
94     buildImage(sampleRow, sampleCol);
95
96     if (showAnnotation)
97     {
98       renderer.updateFromAlignViewport(av);
99       for (int col = 0; col < od.getWidth() && !updateAgain; col++)
100       {
101         mg.translate(col, od.getSequencesHeight());
102         renderer.drawGraph(mg, av.getAlignmentConservationAnnotation(),
103                 av.getAlignmentConservationAnnotation().annotations,
104                 (int) (sampleCol) + 1, od.getGraphHeight(),
105                 (int) (col * sampleCol), (int) (col * sampleCol) + 1);
106         mg.translate(-col, -od.getSequencesHeight());
107
108       }
109     }
110     System.gc();
111
112     updating = false;
113
114     if (updateAgain)
115     {
116       updateAgain = false;
117       op.updateOverviewImage();
118     }
119     else
120     {
121       lastMiniMe = miniMe;
122     }
123   }
124
125   @Override
126   public void paintComponent(Graphics g)
127   {
128     if (updating || updateAgain)
129     {
130       if (lastMiniMe == null)
131       {
132         g.setColor(Color.white);
133         g.fillRect(0, 0, getWidth(), getHeight());
134       }
135       else
136       {
137         g.drawImage(lastMiniMe, 0, 0, getWidth(), getHeight(), this);
138       }
139       g.setColor(TRANS_GREY);
140       g.fillRect(0, 0, getWidth(), getHeight());
141     }
142     else if (lastMiniMe != null)
143     {
144       g.drawImage(lastMiniMe, 0, 0, this);
145       if (lastMiniMe != miniMe)
146       {
147         g.setColor(TRANS_GREY);
148         g.fillRect(0, 0, getWidth(), getHeight());
149       }
150     }
151
152     g.setColor(Color.red);
153     od.drawBox(g);
154   }
155
156   /*
157    * Build the overview panel image
158    */
159   private void buildImage(float sampleRow, float sampleCol)
160   {
161     int lastcol = -1;
162     int lastrow = -1;
163     int rgbcolor = Color.white.getRGB();
164
165     SequenceI seq = null;
166     FeatureColourFinder finder = new FeatureColourFinder(fr);
167
168     final boolean hasHiddenCols = av.hasHiddenColumns();
169     boolean hiddenRow = false;
170     // get hidden row and hidden column map once at beginning.
171     // clone featureRenderer settings to avoid race conditions... if state is
172     // updated just need to refresh again
173     for (int row = 0; row < od.getSequencesHeight() && !updateAgain; row++)
174     {
175       boolean doCopy = true;
176       int currentrow = (int) (row * sampleRow);
177       if (currentrow != lastrow)
178       {
179         doCopy = false;
180
181         lastrow = currentrow;
182
183         // get the sequence which would be at alignment index 'lastrow' if no
184         // rows were hidden, and determine whether it is hidden or not
185         hiddenRow = av.getAlignment().isHidden(lastrow);
186         seq = av.getAlignment().getSequenceAtAbsoluteIndex(lastrow);
187       }
188
189       for (int col = 0; col < od.getWidth() && !updateAgain; col++)
190       {
191         if (doCopy)
192         {
193           rgbcolor = miniMe.getRGB(col, row - 1);
194         }
195         else if ((int) (col * sampleCol) != lastcol
196                 || (int) (row * sampleRow) != lastrow)
197         {
198           lastcol = (int) (col * sampleCol);
199           rgbcolor = getColumnColourFromSequence(seq, hiddenRow,
200                   hasHiddenCols, lastcol, finder);
201         }
202         // else we just use the color we already have , so don't need to set it
203
204         miniMe.setRGB(col, row, rgbcolor);
205       }
206     }
207   }
208
209   /*
210    * Find the colour of a sequence at a specified column position
211    */
212   private int getColumnColourFromSequence(jalview.datamodel.SequenceI seq,
213           boolean hiddenRow, boolean hasHiddenCols, int lastcol,
214           FeatureColourFinder finder)
215   {
216     Color color = Color.white;
217
218     if ((seq != null) && (seq.getLength() > lastcol))
219     {
220       color = sr.getResidueColour(seq, lastcol, finder);
221     }
222
223     if (hiddenRow
224             || (hasHiddenCols && !av.getColumnSelection()
225                     .isVisible(lastcol)))
226     {
227       color = color.darker().darker();
228     }
229
230     return color.getRGB();
231   }
232
233 }