Merge branch 'JAL-1254-hotfix' into develop
[jalview.git] / src / jalview / renderer / AnnotationRenderer.java
1 /*
2  * Jalview - A Sequence Alignment Editor and Viewer (Version 2.8)
3  * Copyright (C) 2012 J Procter, AM Waterhouse, LM Lui, J Engelhardt, G Barton, M Clamp, S Searle
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 of the License, or (at your option) any later version.
10  *  
11  * Jalview is distributed in the hope that it will be useful, but 
12  * WITHOUT ANY WARRANTY; without even the implied warranty 
13  * of MERCHANTABILITY or FITNESS FOR A PARTICULAR 
14  * PURPOSE.  See the GNU General Public License for more details.
15  * 
16  * You should have received a copy of the GNU General Public License along with Jalview.  If not, see <http://www.gnu.org/licenses/>.
17  */
18 package jalview.renderer;
19
20 import jalview.analysis.AAFrequency;
21 import jalview.analysis.StructureFrequency;
22 import jalview.api.AlignViewportI;
23 import jalview.datamodel.AlignmentAnnotation;
24 import jalview.datamodel.Annotation;
25 import jalview.datamodel.ColumnSelection;
26 import jalview.schemes.ColourSchemeI;
27
28 import java.awt.BasicStroke;
29 import java.awt.Color;
30 import java.awt.Font;
31 import java.awt.FontMetrics;
32 import java.awt.Graphics;
33 import java.awt.Graphics2D;
34 import java.awt.Image;
35 import java.awt.font.LineMetrics;
36 import java.awt.geom.AffineTransform;
37 import java.awt.image.ImageObserver;
38 import java.util.Hashtable;
39
40 import com.stevesoft.pat.Regex;
41
42 public class AnnotationRenderer
43 {
44
45   public AnnotationRenderer()
46   {
47     // TODO Auto-generated constructor stub
48   }
49
50   public void drawStemAnnot(Graphics g, Annotation[] row_annotations,
51           int lastSSX, int x, int y, int iconOffset, int startRes,
52           int column, boolean validRes, boolean validEnd)
53   {
54     g.setColor(STEM_COLOUR);
55     int sCol = (lastSSX / charWidth) + startRes;
56     int x1 = lastSSX;
57     int x2 = (x * charWidth);
58     Regex closeparen = new Regex("(\\))");
59
60     String dc = (column == 0 || row_annotations[column - 1] == null) ? ""
61             : row_annotations[column - 1].displayCharacter;
62
63     boolean diffupstream = sCol == 0 || row_annotations[sCol - 1] == null
64             || !dc.equals(row_annotations[sCol - 1].displayCharacter);
65     boolean diffdownstream = !validRes || !validEnd
66             || row_annotations[column] == null
67             || !dc.equals(row_annotations[column].displayCharacter);
68     // System.out.println("Column "+column+" diff up: "+diffupstream+" down:"+diffdownstream);
69     // If a closing base pair half of the stem, display a backward arrow
70     if (column > 0 && closeparen.search(dc))
71     {
72         
73       if (diffupstream)
74       // if (validRes && column>1 && row_annotations[column-2]!=null &&
75       // dc.equals(row_annotations[column-2].displayCharacter))
76       {
77         g.fillPolygon(new int[]
78         { lastSSX + 5, lastSSX + 5, lastSSX }, new int[]
79         { y + iconOffset, y + 14 + iconOffset, y + 8 + iconOffset }, 3);
80         x1 += 5;
81       }
82       if (diffdownstream)
83       {
84         x2 -= 1;
85       }
86     }
87     else
88     {
89         
90       // display a forward arrow
91       if (diffdownstream)
92       {
93         g.fillPolygon(new int[]
94         { x2 - 5, x2 - 5, x2 }, new int[]
95         { y + iconOffset, y + 14 + iconOffset, y + 8 + iconOffset }, 3);
96         x2 -= 5;
97       }
98       if (diffupstream)
99       {
100         x1 += 1;
101       }
102     }
103     // draw arrow body
104     g.fillRect(x1, y + 4 + iconOffset, x2 - x1, 7);
105   }
106
107   private int charWidth, endRes, charHeight;
108
109   private boolean validCharWidth, hasHiddenColumns;
110
111   private FontMetrics fm;
112
113   private final boolean MAC = new jalview.util.Platform().isAMac();
114
115   boolean av_renderHistogram = true, av_renderProfile = true,
116           av_normaliseProfile = false;
117
118   ColourSchemeI profcolour = null;
119
120   private ColumnSelection columnSelection;
121
122   private Hashtable[] hconsensus;
123
124   private Hashtable[] hStrucConsensus;
125
126   private boolean av_ignoreGapsConsensus;
127
128   /**
129    * attributes set from AwtRenderPanelI
130    */
131   /**
132    * old image used when data is currently being calculated and cannot be
133    * rendered
134    */
135   private Image fadedImage;
136
137   /**
138    * panel being rendered into
139    */
140   private ImageObserver annotationPanel;
141
142   /**
143    * width of image to render in panel
144    */
145   private int imgWidth;
146
147   
148   public void drawNotCanonicalAnnot(Graphics g, Color nonCanColor, Annotation[] row_annotations,
149           int lastSSX, int x, int y, int iconOffset, int startRes,
150           int column, boolean validRes, boolean validEnd)
151   {
152         //System.out.println(nonCanColor);
153         
154     g.setColor(nonCanColor);
155     int sCol = (lastSSX / charWidth) + startRes;
156     int x1 = lastSSX;
157     int x2 = (x * charWidth);
158     Regex closeparen = new Regex("}|]|<|a|b|c|d|e|f|g|h|i|j|k|l|m|n|o|p|q|r|s|t|u|v|w|x|y|z");
159     
160     String dc = (column == 0 || row_annotations[column - 1] == null) ? ""
161             : row_annotations[column - 1].displayCharacter;
162
163     boolean diffupstream = sCol == 0 || row_annotations[sCol - 1] == null
164             || !dc.equals(row_annotations[sCol - 1].displayCharacter);
165     boolean diffdownstream = !validRes || !validEnd
166             || row_annotations[column] == null
167             || !dc.equals(row_annotations[column].displayCharacter);
168     // System.out.println("Column "+column+" diff up: "+diffupstream+" down:"+diffdownstream);
169     // If a closing base pair half of the stem, display a backward arrow
170     if (column > 0 && closeparen.search(dc))//  closeletter_b.search(dc)||closeletter_c.search(dc)||closeletter_d.search(dc)||closecrochet.search(dc)) )
171     {
172         
173       if (diffupstream)
174       // if (validRes && column>1 && row_annotations[column-2]!=null &&
175       // dc.equals(row_annotations[column-2].displayCharacter))
176       {
177         g.fillPolygon(new int[]
178         { lastSSX + 5, lastSSX + 5, lastSSX }, new int[]
179         { y + iconOffset, y + 14 + iconOffset, y + 8 + iconOffset }, 3);
180         x1 += 5;
181       }
182       if (diffdownstream)
183       {
184         x2 -= 1;
185       }
186     }
187     else
188     {
189         
190       // display a forward arrow
191       if (diffdownstream)
192       {
193         g.fillPolygon(new int[]
194         { x2 - 5, x2 - 5, x2 }, new int[]
195         { y + iconOffset, y + 14 + iconOffset, y + 8 + iconOffset }, 3);
196         x2 -= 5;
197       }
198       if (diffupstream)
199       {
200         x1 += 1;
201       }
202     }
203     // draw arrow body
204     g.fillRect(x1, y + 4 + iconOffset, x2 - x1, 7);
205   }
206   // public void updateFromAnnotationPanel(FontMetrics annotFM, AlignViewportI
207   // av)
208   public void updateFromAwtRenderPanel(AwtRenderPanelI annotPanel,
209           AlignViewportI av)
210   {
211     fm = annotPanel.getFontMetrics();
212     annotationPanel = annotPanel;
213     fadedImage = annotPanel.getFadedImage();
214     imgWidth = annotPanel.getFadedImageWidth();
215     updateFromAlignViewport(av);
216   }
217
218   public void updateFromAlignViewport(AlignViewportI av)
219   {
220     charWidth = av.getCharWidth();
221     endRes = av.getEndRes();
222     charHeight = av.getCharHeight();
223     hasHiddenColumns = av.hasHiddenColumns();
224     validCharWidth = av.isValidCharWidth();
225     av_renderHistogram = av.isShowConsensusHistogram();
226     av_renderProfile = av.isShowSequenceLogo();
227     av_normaliseProfile = av.isNormaliseSequenceLogo();
228     profcolour = av.getGlobalColourScheme();
229     if (profcolour == null)
230     {
231       // Set the default colour for sequence logo if the alignnent has no
232       // colourscheme set
233       profcolour = av.getAlignment().isNucleotide() ? new jalview.schemes.NucleotideColourScheme()
234               : new jalview.schemes.ZappoColourScheme();
235     }
236     columnSelection = av.getColumnSelection();
237     hconsensus = av.getSequenceConsensusHash();// hconsensus;
238     hStrucConsensus = av.getRnaStructureConsensusHash(); // hStrucConsensus;
239     av_ignoreGapsConsensus = av.getIgnoreGapsConsensus();
240   }
241
242   public int[] getProfileFor(AlignmentAnnotation aa, int column)
243   {
244     // TODO : consider refactoring the global alignment calculation
245     // properties/rendering attributes as a global 'alignment group' which holds
246     // all vis settings for the alignment as a whole rather than a subset
247     //
248     if (aa.autoCalculated && aa.label.startsWith("Consensus"))
249     {
250       if (aa.groupRef != null && aa.groupRef.consensusData != null
251               && aa.groupRef.isShowSequenceLogo())
252       {
253         return AAFrequency.extractProfile(
254                 aa.groupRef.consensusData[column],
255                 aa.groupRef.getIgnoreGapsConsensus());
256       }
257       // TODO extend annotation row to enable dynamic and static profile data to
258       // be stored
259       if (aa.groupRef == null && aa.sequenceRef == null && av_renderProfile)
260       {
261         return AAFrequency.extractProfile(hconsensus[column],
262                 av_ignoreGapsConsensus);
263       }
264     }
265     else
266     {
267       if (aa.autoCalculated && aa.label.startsWith("StrucConsensus"))
268       {
269         // TODO implement group structure consensus
270         /*
271          * if (aa.groupRef != null && aa.groupRef.consensusData != null &&
272          * aa.groupRef.isShowSequenceLogo()) { //TODO check what happens for
273          * group selections return StructureFrequency.extractProfile(
274          * aa.groupRef.consensusData[column], aa.groupRef
275          * .getIgnoreGapsConsensus()); }
276          */
277         // TODO extend annotation row to enable dynamic and static profile data
278         // to
279         // be stored
280         if (aa.groupRef == null && aa.sequenceRef == null
281                 && av_renderProfile && hStrucConsensus != null
282                 && hStrucConsensus.length > column)
283         {
284           return StructureFrequency.extractProfile(hStrucConsensus[column],
285                   av_ignoreGapsConsensus);
286         }
287       }
288     }
289     return null;
290   }
291
292   /**
293    * Render the annotation rows associated with an alignment.
294    * 
295    * @param annotPanel
296    *          container frame
297    * @param av
298    *          data and view settings to render
299    * @param g
300    *          destination for graphics
301    * @param activeRow
302    *          row where a mouse event occured (or -1)
303    * @param startRes
304    *          first column that will be drawn
305    * @param endRes
306    *          last column that will be drawn
307    * @return true if the fadedImage was used for any alignment annotation rows
308    *         currently being calculated
309    */
310   public boolean drawComponent(AwtRenderPanelI annotPanel,
311           AlignViewportI av, Graphics g, int activeRow, int startRes,
312           int endRes)
313   {
314     boolean usedFaded = false;
315     // NOTES:
316     // AnnotationPanel needs to implement: ImageObserver, access to
317     // AlignViewport
318     updateFromAwtRenderPanel(annotPanel, av);
319     fm = g.getFontMetrics();
320     AlignmentAnnotation[] aa = av.getAlignment().getAlignmentAnnotation();
321     if (aa==null)
322     {
323       return false;
324     }
325     int x = 0, y = 0;
326     int column = 0;
327     char lastSS;
328     int lastSSX;
329     int iconOffset = 0;
330     boolean validRes = false;
331     boolean validEnd = false;
332     boolean labelAllCols = false;
333     boolean centreColLabels, centreColLabelsDef = av
334             .getCentreColumnLabels();
335     boolean scaleColLabel = false;
336     boolean[] graphGroupDrawn = new boolean[aa.length];
337     int charOffset = 0; // offset for a label
338     float fmWidth, fmScaling = 1f; // scaling for a label to fit it into a
339     // column.
340     Font ofont = g.getFont();
341     // \u03B2 \u03B1
342     for (int i = 0; i < aa.length; i++)
343     {
344       AlignmentAnnotation row = aa[i];
345       Annotation[] row_annotations = row.annotations;
346       if (!row.visible)
347       {
348         continue;
349       }
350       centreColLabels = row.centreColLabels || centreColLabelsDef;
351       labelAllCols = row.showAllColLabels;
352       scaleColLabel = row.scaleColLabel;
353       lastSS = ' ';
354       lastSSX = 0;
355       if (row.graph > 0)
356       {
357         if (row.graphGroup > -1 && graphGroupDrawn[row.graphGroup])
358         {
359           continue;
360         }
361
362         // this is so that we draw the characters below the graph
363         y += row.height;
364
365         if (row.hasText)
366         {
367           iconOffset = charHeight - fm.getDescent();
368           y -= charHeight;
369         }
370       }
371       else if (row.hasText)
372       {
373         iconOffset = charHeight - fm.getDescent();
374
375       }
376       else
377       {
378         iconOffset = 0;
379       }
380
381       if (row.autoCalculated && av.isCalculationInProgress(row))
382       {
383         y += charHeight;
384         usedFaded = true;
385         g.drawImage(fadedImage, 0, y - row.height, imgWidth, y, 0, y
386                 - row.height, imgWidth, y, annotationPanel);
387         g.setColor(Color.black);
388         // g.drawString("Calculating "+aa[i].label+"....",20, y-row.height/2);
389
390         continue;
391       }
392
393       /*
394        * else if (annotationPanel.av.updatingConservation &&
395        * aa[i].label.equals("Conservation")) {
396        * 
397        * y += charHeight; g.drawImage(annotationPanel.fadedImage, 0, y -
398        * row.height, annotationPanel.imgWidth, y, 0, y - row.height,
399        * annotationPanel.imgWidth, y, annotationPanel);
400        * 
401        * g.setColor(Color.black); //
402        * g.drawString("Calculating Conservation.....",20, y-row.height/2);
403        * 
404        * continue; } else if (annotationPanel.av.updatingConservation &&
405        * aa[i].label.equals("Quality")) {
406        * 
407        * y += charHeight; g.drawImage(annotationPanel.fadedImage, 0, y -
408        * row.height, annotationPanel.imgWidth, y, 0, y - row.height,
409        * annotationPanel.imgWidth, y, annotationPanel); g.setColor(Color.black);
410        * // / g.drawString("Calculating Quality....",20, y-row.height/2);
411        * 
412        * continue; }
413        */
414       // first pass sets up state for drawing continuation from left-hand column
415       // of startRes
416       x = (startRes == 0) ? 0 : -1;
417       while (x < endRes - startRes)
418       {
419         if (hasHiddenColumns)
420         {
421           column = columnSelection.adjustForHiddenColumns(startRes + x);
422           if (column > row_annotations.length - 1)
423           {
424             break;
425           }
426         }
427         else
428         {
429           column = startRes + x;
430         }
431
432         if ((row_annotations == null) || (row_annotations.length <= column)
433                 || (row_annotations[column] == null))
434         {
435           validRes = false;
436         }
437         else
438         {
439           validRes = true;
440         }
441         if (x > -1)
442         {
443           if (activeRow == i)
444           {
445             g.setColor(Color.red);
446
447             if (columnSelection != null)
448             {
449               for (int n = 0; n < columnSelection.size(); n++)
450               {
451                 int v = columnSelection.columnAt(n);
452
453                 if (v == column)
454                 {
455                   g.fillRect(x * charWidth, y, charWidth, charHeight);
456                 }
457               }
458             }
459           }
460           if (!row.isValidStruc())
461           {
462             g.setColor(Color.orange);
463             g.fillRect((int) row.getInvalidStrucPos() * charWidth, y,
464                     charWidth, charHeight);
465           }
466           if (validCharWidth
467                   && validRes
468                   && row_annotations[column].displayCharacter != null
469                   && (row_annotations[column].displayCharacter.length() > 0))
470           {
471
472             if (centreColLabels || scaleColLabel)
473             {
474               fmWidth = fm.charsWidth(
475                       row_annotations[column].displayCharacter
476                               .toCharArray(), 0,
477                       row_annotations[column].displayCharacter.length());
478
479               if (scaleColLabel)
480               {
481                 // justify the label and scale to fit in column
482                 if (fmWidth > charWidth)
483                 {
484                   // scale only if the current font isn't already small enough
485                   fmScaling = charWidth;
486                   fmScaling /= fmWidth;
487                   g.setFont(ofont.deriveFont(AffineTransform
488                           .getScaleInstance(fmScaling, 1.0)));
489                   // and update the label's width to reflect the scaling.
490                   fmWidth = charWidth;
491                 }
492               }
493             }
494             else
495             {
496               fmWidth = fm
497                       .charWidth(row_annotations[column].displayCharacter
498                               .charAt(0));
499             }
500             charOffset = (int) ((charWidth - fmWidth) / 2f);
501
502             if (row_annotations[column].colour == null)
503               g.setColor(Color.black);
504             else
505               g.setColor(row_annotations[column].colour);
506
507             if (column == 0 || row.graph > 0)
508             {
509               g.drawString(row_annotations[column].displayCharacter,
510                       (x * charWidth) + charOffset, y + iconOffset);
511             }
512             else if (row_annotations[column - 1] == null
513                     || (labelAllCols
514                             || !row_annotations[column].displayCharacter
515                                     .equals(row_annotations[column - 1].displayCharacter) || (row_annotations[column].displayCharacter
516                             .length() < 2 && row_annotations[column].secondaryStructure == ' ')))
517             {
518                 g.drawString(row_annotations[column].displayCharacter
519                           , x
520                       * charWidth + charOffset, y + iconOffset);
521             }
522             g.setFont(ofont);
523           }
524         }
525         if (row.hasIcons)
526         {
527           char ss = validRes ? row_annotations[column].secondaryStructure
528                   : '-';
529           
530           if (ss == '(')
531           {
532             // distinguish between forward/backward base-pairing
533             if (row_annotations[column].displayCharacter.indexOf(')') > -1)
534             {
535             
536               ss = ')';
537               
538             }
539           }
540            if (ss == '[')
541           {
542             if ((row_annotations[column].displayCharacter.indexOf(']') > -1))
543             {
544                 ss = ']';
545                 
546                 
547             }
548           }
549            if (ss == '{')
550            {
551              // distinguish between forward/backward base-pairing
552              if (row_annotations[column].displayCharacter.indexOf('}') > -1)
553              {
554                ss = '}';
555                
556                
557              }
558            }
559            if (ss == '<')
560            {
561              // distinguish between forward/backward base-pairing
562              if (row_annotations[column].displayCharacter.indexOf('<') > -1)
563              {
564                ss = '>';
565                
566                
567              }
568            }
569            if (ss == 'A')
570            {
571              // distinguish between forward/backward base-pairing
572              if (row_annotations[column].displayCharacter.indexOf('a') > -1)
573              {
574                ss = 'a';
575               
576                
577              }
578            }
579            
580            if (ss == 'B')
581            {
582              // distinguish between forward/backward base-pairing
583              if (row_annotations[column].displayCharacter.indexOf('b') > -1)
584              {
585                ss = 'b';
586                
587              }
588            }
589            
590            if (ss == 'C')
591            {
592              // distinguish between forward/backward base-pairing
593              if (row_annotations[column].displayCharacter.indexOf('c') > -1)
594              {
595                ss = 'c';
596                
597              }
598            }
599            if (ss == 'D')
600            {
601              // distinguish between forward/backward base-pairing
602              if (row_annotations[column].displayCharacter.indexOf('d') > -1)
603              {
604                ss = 'd';
605                
606              }
607            }
608            if (ss == '1')
609            {
610              // distinguish between forward/backward base-pairing
611              if (row_annotations[column].displayCharacter.indexOf('e') > -1)
612              {
613                ss = 'e';
614                
615              }
616            }
617            if (ss == 'F')
618            {
619              // distinguish between forward/backward base-pairing
620              if (row_annotations[column].displayCharacter.indexOf('f') > -1)
621              {
622                ss = 'f';
623                
624              }
625            }
626            if (ss == 'G')
627            {
628              // distinguish between forward/backward base-pairing
629              if (row_annotations[column].displayCharacter.indexOf('g') > -1)
630              {
631                ss = 'g';
632                
633              }
634            }
635            if (ss == '2')
636            {
637              // distinguish between forward/backward base-pairing
638              if (row_annotations[column].displayCharacter.indexOf('h') > -1)
639              {
640                ss = 'h';
641                
642              }
643            }
644            if (ss == 'I')
645            {
646              // distinguish between forward/backward base-pairing
647              if (row_annotations[column].displayCharacter.indexOf('i') > -1)
648              {
649                ss = 'i';
650                
651              }
652            }
653            if (ss == 'J')
654            {
655              // distinguish between forward/backward base-pairing
656              if (row_annotations[column].displayCharacter.indexOf('j') > -1)
657              {
658                ss = 'j';
659                
660              }
661            }
662            if (ss == 'K')
663            {
664              // distinguish between forward/backward base-pairing
665              if (row_annotations[column].displayCharacter.indexOf('k') > -1)
666              {
667                ss = 'k';
668                
669              }
670            }
671            if (ss == 'L')
672            {
673              // distinguish between forward/backward base-pairing
674              if (row_annotations[column].displayCharacter.indexOf('l') > -1)
675              {
676                ss = 'l';
677                
678              }
679            }
680            if (ss == 'M')
681            {
682              // distinguish between forward/backward base-pairing
683              if (row_annotations[column].displayCharacter.indexOf('m') > -1)
684              {
685                ss = 'm';
686                
687              }
688            }
689            if (ss == 'N')
690            {
691              // distinguish between forward/backward base-pairing
692              if (row_annotations[column].displayCharacter.indexOf('n') > -1)
693              {
694                ss = 'n';
695                
696              }
697            }
698            if (ss == 'O')
699            {
700              // distinguish between forward/backward base-pairing
701              if (row_annotations[column].displayCharacter.indexOf('o') > -1)
702              {
703                ss = 'o';
704                
705              }
706            }
707            if (ss == 'P')
708            {
709              // distinguish between forward/backward base-pairing
710              if (row_annotations[column].displayCharacter.indexOf('p') > -1)
711              {
712                ss = 'p';
713                
714              }
715            }
716            if (ss == 'Q')
717            {
718              // distinguish between forward/backward base-pairing
719              if (row_annotations[column].displayCharacter.indexOf('q') > -1)
720              {
721                ss = 'q';
722                
723              }
724            }
725            if (ss == 'R')
726            {
727              // distinguish between forward/backward base-pairing
728              if (row_annotations[column].displayCharacter.indexOf('r') > -1)
729              {
730                ss = 'r';
731                
732              }
733            }
734            if (ss == 'S')
735            {
736              // distinguish between forward/backward base-pairing
737              if (row_annotations[column].displayCharacter.indexOf('s') > -1)
738              {
739                ss = 's';
740                
741              }
742            }
743            if (ss == 'T')
744            {
745              // distinguish between forward/backward base-pairing
746              if (row_annotations[column].displayCharacter.indexOf('t') > -1)
747              {
748                ss = 't';
749                
750              }
751            }
752            if (ss == 'U')
753            {
754              // distinguish between forward/backward base-pairing
755              if (row_annotations[column].displayCharacter.indexOf('u') > -1)
756              {
757                ss = 'u';
758                
759              }
760            }
761            if (ss == 'V')
762            {
763              // distinguish between forward/backward base-pairing
764              if (row_annotations[column].displayCharacter.indexOf('v') > -1)
765              {
766                ss = 'v';
767                
768              }
769            }
770            if (ss == 'W')
771            {
772              // distinguish between forward/backward base-pairing
773              if (row_annotations[column].displayCharacter.indexOf('w') > -1)
774              {
775                ss = 'w';
776                
777              }
778            }
779            if (ss == 'X')
780            {
781              // distinguish between forward/backward base-pairing
782              if (row_annotations[column].displayCharacter.indexOf('x') > -1)
783              {
784                ss = 'x';
785                
786              }
787            }
788            if (ss == 'Y')
789            {
790              // distinguish between forward/backward base-pairing
791              if (row_annotations[column].displayCharacter.indexOf('y') > -1)
792              {
793                ss = 'y';
794                
795              }
796            }
797            if (ss == 'Z')
798            {
799              // distinguish between forward/backward base-pairing
800              if (row_annotations[column].displayCharacter.indexOf('z') > -1)
801              {
802                ss = 'z';
803                
804              }
805            }
806           if (!validRes || (ss != lastSS))
807           {
808             if (x > -1)
809             {
810               switch (lastSS)
811               {
812               case 'H':
813                 drawHelixAnnot(g, row_annotations, lastSSX, x, y,
814                         iconOffset, startRes, column, validRes, validEnd);
815                 break;
816
817               case 'E':
818                 drawSheetAnnot(g, row_annotations, lastSSX, x, y,
819                         iconOffset, startRes, column, validRes, validEnd);
820                 break;
821
822               case '(': // Stem case for RNA secondary structure
823               case ')': // and opposite direction
824                 drawStemAnnot(g, row_annotations, lastSSX, x, y, iconOffset, startRes,
825                         column, validRes, validEnd);
826 //              case 'S': // Stem case for RNA secondary structure
827 //              case 's': // and opposite direction
828 //                drawStemAnnot(g, row_annotations, lastSSX, x, y,
829 //                        iconOffset, startRes, column, validRes, validEnd);
830                 break;
831               case '{':
832               case '}':
833               case '[':
834               case ']':
835               case '>':
836               case '<':
837               case 'A':
838               case 'a':
839               case 'B':
840               case 'b':
841               case 'C':
842               case 'c':
843               case 'D':
844               case 'd':
845               case '1':
846               case 'e':
847               case 'F':
848               case 'f':
849               case 'G':
850               case 'g':
851               case '2':
852               case 'h':
853               case 'I':
854               case 'i':
855               case 'J':
856               case 'j':
857               case 'K':
858               case 'k':
859               case 'L':
860               case 'l':
861               case 'M':
862               case 'm':
863               case 'N':
864               case 'n':
865               case 'O':
866               case 'o':
867               case 'P':
868               case 'p':
869               case 'Q':
870               case 'q':
871               case 'R':
872               case 'r':
873               case 'S':
874               case 's':
875               case 'T':
876               case 't':
877               case 'U':
878               case 'u':
879               case 'V':
880               case 'v':
881               case 'W':
882               case 'w':
883               case 'X':
884               case 'x':
885               case 'Y':
886               case 'y':
887               case 'Z':
888               case 'z':
889                   //System.out.println(lastSS);
890                   Color nonCanColor= getNotCanonicalColor(lastSS);
891                   drawNotCanonicalAnnot(g, nonCanColor, row_annotations, lastSSX, x, y, iconOffset, startRes,
892                           column, validRes, validEnd);
893                   break;
894               default:
895                 g.setColor(Color.gray);
896                 g.fillRect(lastSSX, y + 6 + iconOffset, (x * charWidth)
897                         - lastSSX, 2);
898
899                 break;
900               }
901             }
902             if (validRes)
903             {
904               lastSS = ss;
905             }
906             else
907             {
908               lastSS = ' ';
909             }
910             if (x > -1)
911             {
912               lastSSX = (x * charWidth);
913             }
914           }
915         }
916         column++;
917         x++;
918       }
919       if (column >= row_annotations.length)
920       {
921         column = row_annotations.length - 1;
922         validEnd = false;
923       }
924       else
925       {
926         validEnd = true;
927       }
928       if ((row_annotations == null) || (row_annotations.length <= column)
929               || (row_annotations[column] == null))
930       {
931         validRes = false;
932       }
933       else
934       {
935         validRes = true;
936       }
937
938       // x ++;
939
940       if (row.hasIcons)
941       {
942         switch (lastSS)
943         {
944         case 'H':
945           drawHelixAnnot(g, row_annotations, lastSSX, x, y, iconOffset,
946                   startRes, column, validRes, validEnd);
947           break;
948
949         case 'E':
950           drawSheetAnnot(g, row_annotations, lastSSX, x, y, iconOffset,
951                   startRes, column, validRes, validEnd);
952           break;
953         case 's':
954         case 'S': // Stem case for RNA secondary structure
955           drawStemAnnot(g, row_annotations, lastSSX, x, y, iconOffset,
956                   startRes, column, validRes, validEnd);
957           break;
958         case '{':
959         case '}':
960         case '[':
961         case ']':
962         case '>':
963         case '<':
964         case 'A':
965         case 'a':
966         case 'B':
967         case 'b':
968         case 'C':
969         case 'c':
970         case 'D':
971         case 'd':
972         case '1':
973         case 'e':
974         case 'F':
975         case 'f':
976         case 'G':
977         case 'g':
978         case '2':
979         case 'h':
980         case 'I':
981         case 'i':
982         case 'J':
983         case 'j':
984         case 'K':
985         case 'k':
986         case 'L':
987         case 'l':
988         case 'M':
989         case 'm':
990         case 'N':
991         case 'n':
992         case 'O':
993         case 'o':
994         case 'P':
995         case 'p':
996         case 'Q':
997         case 'q':
998         case 'R':
999         case 'r':
1000         case 'T':
1001         case 't':
1002         case 'U':
1003         case 'u':
1004         case 'V':
1005         case 'v':
1006         case 'W':
1007         case 'w':
1008         case 'X':
1009         case 'x':
1010         case 'Y':
1011         case 'y':
1012         case 'Z':
1013         case 'z':
1014                 //System.out.println(lastSS);
1015           Color nonCanColor = getNotCanonicalColor(lastSS);
1016           drawNotCanonicalAnnot(g,nonCanColor, row_annotations, lastSSX, x, y, iconOffset, startRes,
1017                     column, validRes, validEnd);
1018           break;
1019         default:
1020           drawGlyphLine(g, row_annotations, lastSSX, x, y, iconOffset,
1021                   startRes, column, validRes, validEnd);
1022           break;
1023         }
1024       }
1025
1026       if (row.graph > 0 && row.graphHeight > 0)
1027       {
1028         if (row.graph == AlignmentAnnotation.LINE_GRAPH)
1029         {
1030           if (row.graphGroup > -1 && !graphGroupDrawn[row.graphGroup])
1031           {
1032             float groupmax = -999999, groupmin = 9999999;
1033             for (int gg = 0; gg < aa.length; gg++)
1034             {
1035               if (aa[gg].graphGroup != row.graphGroup)
1036               {
1037                 continue;
1038               }
1039
1040               if (aa[gg] != row)
1041               {
1042                 aa[gg].visible = false;
1043               }
1044               if (aa[gg].graphMax > groupmax)
1045               {
1046                 groupmax = aa[gg].graphMax;
1047               }
1048               if (aa[gg].graphMin < groupmin)
1049               {
1050                 groupmin = aa[gg].graphMin;
1051               }
1052             }
1053
1054             for (int gg = 0; gg < aa.length; gg++)
1055             {
1056               if (aa[gg].graphGroup == row.graphGroup)
1057               {
1058                 drawLineGraph(g, aa[gg], aa[gg].annotations, startRes,
1059                         endRes, y, groupmin, groupmax, row.graphHeight);
1060               }
1061             }
1062
1063             graphGroupDrawn[row.graphGroup] = true;
1064           }
1065           else
1066           {
1067             drawLineGraph(g, row, row_annotations, startRes, endRes, y,
1068                     row.graphMin, row.graphMax, row.graphHeight);
1069           }
1070         }
1071         else if (row.graph == AlignmentAnnotation.BAR_GRAPH)
1072         {
1073           drawBarGraph(g, row, row_annotations, startRes, endRes,
1074                   row.graphMin, row.graphMax, y);
1075         }
1076       }
1077
1078       if (row.graph > 0 && row.hasText)
1079       {
1080         y += charHeight;
1081       }
1082
1083       if (row.graph == 0)
1084       {
1085         y += aa[i].height;
1086       }
1087     }
1088     return !usedFaded;
1089   }
1090
1091   private final Color GLYPHLINE_COLOR = Color.gray;
1092
1093   private final Color SHEET_COLOUR = Color.green;
1094
1095   private final Color HELIX_COLOUR = Color.red;
1096
1097   private final Color STEM_COLOUR = Color.blue;
1098   
1099   private  Color sdNOTCANONICAL_COLOUR;
1100
1101   public void drawGlyphLine(Graphics g, Annotation[] row, int lastSSX,
1102           int x, int y, int iconOffset, int startRes, int column,
1103           boolean validRes, boolean validEnd)
1104   {
1105     g.setColor(GLYPHLINE_COLOR);
1106     g.fillRect(lastSSX, y + 6 + iconOffset, (x * charWidth) - lastSSX, 2);
1107   }
1108
1109   public void drawSheetAnnot(Graphics g, Annotation[] row, int lastSSX,
1110           int x, int y, int iconOffset, int startRes, int column,
1111           boolean validRes, boolean validEnd)
1112   {
1113     g.setColor(SHEET_COLOUR);
1114
1115     if (!validEnd || !validRes || row == null || row[column] == null
1116             || row[column].secondaryStructure != 'E')
1117     {
1118       g.fillRect(lastSSX, y + 4 + iconOffset,
1119               (x * charWidth) - lastSSX - 4, 7);
1120       g.fillPolygon(new int[]
1121       { (x * charWidth) - 4, (x * charWidth) - 4, (x * charWidth) },
1122               new int[]
1123               { y + iconOffset, y + 14 + iconOffset, y + 7 + iconOffset },
1124               3);
1125     }
1126     else
1127     {
1128       g.fillRect(lastSSX, y + 4 + iconOffset,
1129               (x + 1) * charWidth - lastSSX, 7);
1130     }
1131
1132   }
1133
1134   public void drawHelixAnnot(Graphics g, Annotation[] row, int lastSSX,
1135           int x, int y, int iconOffset, int startRes, int column,
1136           boolean validRes, boolean validEnd)
1137   {
1138     g.setColor(HELIX_COLOUR);
1139
1140     int sCol = (lastSSX / charWidth) + startRes;
1141     int x1 = lastSSX;
1142     int x2 = (x * charWidth);
1143
1144     if (MAC)
1145     {
1146       int ofs = charWidth / 2;
1147       // Off by 1 offset when drawing rects and ovals
1148       // to offscreen image on the MAC
1149       g.fillRoundRect(lastSSX, y + 4 + iconOffset, x2 - x1, 8, 8, 8);
1150       if (sCol == 0 || row[sCol - 1] == null
1151               || row[sCol - 1].secondaryStructure != 'H')
1152       {
1153       }
1154       else
1155       {
1156         // g.setColor(Color.orange);
1157         g.fillRoundRect(lastSSX, y + 4 + iconOffset, x2 - x1 - ofs + 1, 8,
1158                 0, 0);
1159       }
1160       if (!validRes || row[column] == null
1161               || row[column].secondaryStructure != 'H')
1162       {
1163
1164       }
1165       else
1166       {
1167         // g.setColor(Color.magenta);
1168         g.fillRoundRect(lastSSX + ofs, y + 4 + iconOffset, x2 - x1 - ofs
1169                 + 1, 8, 0, 0);
1170
1171       }
1172
1173       return;
1174     }
1175
1176     if (sCol == 0 || row[sCol - 1] == null
1177             || row[sCol - 1].secondaryStructure != 'H')
1178     {
1179       g.fillArc(lastSSX, y + 4 + iconOffset, charWidth, 8, 90, 180);
1180       x1 += charWidth / 2;
1181     }
1182
1183     if (!validRes || row[column] == null
1184             || row[column].secondaryStructure != 'H')
1185     {
1186       g.fillArc((x * charWidth) - charWidth, y + 4 + iconOffset, charWidth,
1187               8, 270, 180);
1188       x2 -= charWidth / 2;
1189     }
1190
1191     g.fillRect(x1, y + 4 + iconOffset, x2 - x1, 8);
1192   }
1193
1194   public void drawLineGraph(Graphics g, AlignmentAnnotation _aa,
1195           Annotation[] aa_annotations, int sRes, int eRes, int y,
1196           float min, float max, int graphHeight)
1197   {
1198     if (sRes > aa_annotations.length)
1199     {
1200       return;
1201     }
1202
1203     int x = 0;
1204
1205     // Adjustment for fastpaint to left
1206     if (eRes < endRes)
1207     {
1208       eRes++;
1209     }
1210
1211     eRes = Math.min(eRes, aa_annotations.length);
1212
1213     if (sRes == 0)
1214     {
1215       x++;
1216     }
1217
1218     int y1 = y, y2 = y;
1219     float range = max - min;
1220
1221     // //Draw origin
1222     if (min < 0)
1223     {
1224       y2 = y - (int) ((0 - min / range) * graphHeight);
1225     }
1226
1227     g.setColor(Color.gray);
1228     g.drawLine(x - charWidth, y2, (eRes - sRes + 1) * charWidth, y2);
1229
1230     eRes = Math.min(eRes, aa_annotations.length);
1231
1232     int column;
1233     int aaMax = aa_annotations.length - 1;
1234
1235     while (x < eRes - sRes)
1236     {
1237       column = sRes + x;
1238       if (hasHiddenColumns)
1239       {
1240         column = columnSelection.adjustForHiddenColumns(column);
1241       }
1242
1243       if (column > aaMax)
1244       {
1245         break;
1246       }
1247
1248       if (aa_annotations[column] == null
1249               || aa_annotations[column - 1] == null)
1250       {
1251         x++;
1252         continue;
1253       }
1254
1255       if (aa_annotations[column].colour == null)
1256         g.setColor(Color.black);
1257       else
1258         g.setColor(aa_annotations[column].colour);
1259
1260       y1 = y
1261               - (int) (((aa_annotations[column - 1].value - min) / range) * graphHeight);
1262       y2 = y
1263               - (int) (((aa_annotations[column].value - min) / range) * graphHeight);
1264
1265       g.drawLine(x * charWidth - charWidth / 2, y1, x * charWidth
1266               + charWidth / 2, y2);
1267       x++;
1268     }
1269
1270     if (_aa.threshold != null)
1271     {
1272       g.setColor(_aa.threshold.colour);
1273       Graphics2D g2 = (Graphics2D) g;
1274       g2.setStroke(new BasicStroke(1, BasicStroke.CAP_SQUARE,
1275               BasicStroke.JOIN_ROUND, 3f, new float[]
1276               { 5f, 3f }, 0f));
1277
1278       y2 = (int) (y - ((_aa.threshold.value - min) / range) * graphHeight);
1279       g.drawLine(0, y2, (eRes - sRes) * charWidth, y2);
1280       g2.setStroke(new BasicStroke());
1281     }
1282   }
1283
1284   public void drawBarGraph(Graphics g, AlignmentAnnotation _aa,
1285           Annotation[] aa_annotations, int sRes, int eRes, float min,
1286           float max, int y)
1287   {
1288     if (sRes > aa_annotations.length)
1289     {
1290       return;
1291     }
1292     Font ofont = g.getFont();
1293     eRes = Math.min(eRes, aa_annotations.length);
1294
1295     int x = 0, y1 = y, y2 = y;
1296
1297     float range = max - min;
1298
1299     if (min < 0)
1300     {
1301       y2 = y - (int) ((0 - min / (range)) * _aa.graphHeight);
1302     }
1303
1304     g.setColor(Color.gray);
1305
1306     g.drawLine(x, y2, (eRes - sRes) * charWidth, y2);
1307
1308     int column;
1309     int aaMax = aa_annotations.length - 1;
1310     boolean renderHistogram = true, renderProfile = true, normaliseProfile = false;
1311     // if (aa.autoCalculated && aa.label.startsWith("Consensus"))
1312     {
1313       // TODO: generalise this to have render styles for consensus/profile data
1314       if (_aa.groupRef != null)
1315       {
1316         renderHistogram = _aa.groupRef.isShowConsensusHistogram();
1317         renderProfile = _aa.groupRef.isShowSequenceLogo();
1318         normaliseProfile = _aa.groupRef.isNormaliseSequenceLogo();
1319       }
1320       else
1321       {
1322         renderHistogram = av_renderHistogram;
1323         renderProfile = av_renderProfile;
1324         normaliseProfile = av_normaliseProfile;
1325       }
1326     }
1327     while (x < eRes - sRes)
1328     {
1329       column = sRes + x;
1330       if (hasHiddenColumns)
1331       {
1332         column = columnSelection.adjustForHiddenColumns(column);
1333       }
1334
1335       if (column > aaMax)
1336       {
1337         break;
1338       }
1339
1340       if (aa_annotations[column] == null)
1341       {
1342         x++;
1343         continue;
1344       }
1345       if (aa_annotations[column].colour == null)
1346         g.setColor(Color.black);
1347       else
1348         g.setColor(aa_annotations[column].colour);
1349
1350       y1 = y
1351               - (int) (((aa_annotations[column].value - min) / (range)) * _aa.graphHeight);
1352
1353       if (renderHistogram)
1354       {
1355         if (y1 - y2 > 0)
1356         {
1357           g.fillRect(x * charWidth, y2, charWidth, y1 - y2);
1358         }
1359         else
1360         {
1361           g.fillRect(x * charWidth, y1, charWidth, y2 - y1);
1362         }
1363       }
1364       // draw profile if available
1365       if (renderProfile)
1366       {
1367
1368         int profl[] = getProfileFor(_aa, column);
1369         // just try to draw the logo if profl is not null
1370         if (profl != null && profl[1] != 0)
1371         {
1372           float ht = normaliseProfile ? y - _aa.graphHeight : y1;
1373           double htn = normaliseProfile ? _aa.graphHeight : (y2 - y1);// aa.graphHeight;
1374           double hght;
1375           float wdth;
1376           double ht2 = 0;
1377           char[] dc;
1378
1379           /**
1380            * profl.length == 74 indicates that the profile of a secondary
1381            * structure conservation row was accesed. Therefore dc gets length 2,
1382            * to have space for a basepair instead of just a single nucleotide
1383            */
1384           if (profl.length == 74)
1385           {
1386             dc = new char[2];
1387           }
1388           else
1389           {
1390             dc = new char[1];
1391           }
1392           LineMetrics lm = g.getFontMetrics(ofont).getLineMetrics("Q", g);
1393           double scale = 1f / (normaliseProfile ? profl[1] : 100f);
1394           float ofontHeight = 1f / lm.getAscent();// magnify to fill box
1395           double scl = 0.0;
1396           for (int c = 2; c < profl[0];)
1397           {
1398             dc[0] = (char) profl[c++];
1399
1400             if (_aa.label.startsWith("StrucConsensus"))
1401             {
1402               dc[1] = (char) profl[c++];
1403             }
1404
1405             wdth = charWidth;
1406             wdth /= fm.charsWidth(dc, 0, dc.length);
1407
1408             ht += scl;
1409             {
1410               scl = htn * scale * profl[c++];
1411               lm = ofont.getLineMetrics(dc, 0, 1, g.getFontMetrics()
1412                       .getFontRenderContext());
1413               g.setFont(ofont.deriveFont(AffineTransform.getScaleInstance(
1414                       wdth, scl / lm.getAscent())));
1415               lm = g.getFontMetrics().getLineMetrics(dc, 0, 1, g);
1416
1417               // Debug - render boxes around characters
1418               // g.setColor(Color.red);
1419               // g.drawRect(x*av.charWidth, (int)ht, av.charWidth,
1420               // (int)(scl));
1421               // g.setColor(profcolour.findColour(dc[0]).darker());
1422               g.setColor(profcolour.findColour(dc[0], column, null));
1423
1424               hght = (ht + (scl - lm.getDescent() - lm.getBaselineOffsets()[lm
1425                       .getBaselineIndex()]));
1426
1427               g.drawChars(dc, 0, dc.length, x * charWidth, (int) hght);
1428             }
1429           }
1430           g.setFont(ofont);
1431         }
1432       }
1433       x++;
1434     }
1435     if (_aa.threshold != null)
1436     {
1437       g.setColor(_aa.threshold.colour);
1438       Graphics2D g2 = (Graphics2D) g;
1439       g2.setStroke(new BasicStroke(1, BasicStroke.CAP_SQUARE,
1440               BasicStroke.JOIN_ROUND, 3f, new float[]
1441               { 5f, 3f }, 0f));
1442
1443       y2 = (int) (y - ((_aa.threshold.value - min) / range)
1444               * _aa.graphHeight);
1445       g.drawLine(0, y2, (eRes - sRes) * charWidth, y2);
1446       g2.setStroke(new BasicStroke());
1447     }
1448   }
1449
1450   // used by overview window
1451   public void drawGraph(Graphics g, AlignmentAnnotation _aa,
1452           Annotation[] aa_annotations, int width, int y, int sRes, int eRes)
1453   {
1454     eRes = Math.min(eRes, aa_annotations.length);
1455     g.setColor(Color.white);
1456     g.fillRect(0, 0, width, y);
1457     g.setColor(new Color(0, 0, 180));
1458
1459     int x = 0, height;
1460
1461     for (int j = sRes; j < eRes; j++)
1462     {
1463       if (aa_annotations[j] != null)
1464       {
1465         if (aa_annotations[j].colour == null)
1466           g.setColor(Color.black);
1467         else
1468           g.setColor(aa_annotations[j].colour);
1469
1470         height = (int) ((aa_annotations[j].value / _aa.graphMax) * y);
1471         if (height > y)
1472         {
1473           height = y;
1474         }
1475
1476         g.fillRect(x, y - height, charWidth, height);
1477       }
1478       x += charWidth;
1479     }
1480   }
1481
1482   
1483   Color getNotCanonicalColor(char lastss)
1484         {
1485           switch (lastss)
1486       {
1487           case '{':  
1488               case '}':
1489                   return Color.cyan;
1490                  
1491               case '[':
1492               case ']':
1493                   return Color.green;
1494                  
1495               case '>':
1496               case '<':
1497                   return Color.magenta;
1498                   
1499               case 'A':
1500               case 'a':
1501                   return Color.orange;
1502                 
1503               case 'B':
1504               case 'b':
1505                   return Color.pink;
1506                   
1507               case 'C':
1508               case 'c':
1509                   return Color.red;
1510                  
1511               case 'D':
1512               case 'd':
1513                   return Color.yellow;
1514                   
1515               case '1':
1516               case 'e':
1517                   return Color.black;
1518                  
1519               case 'F':
1520               case 'f':
1521                   return Color.darkGray;
1522                  
1523               case 'G':
1524               case 'g':
1525                   return Color.gray;
1526                 
1527               case '2':
1528               case 'h':
1529                   return Color.lightGray;
1530                   
1531               case 'I':
1532               case 'i':
1533                   return Color.white;
1534                  
1535               case 'J':
1536               case 'j':
1537                   return Color.cyan;
1538                   
1539               case 'K':
1540               case 'k':
1541                   return Color.magenta;
1542                 
1543               case 'L':
1544               case 'l':
1545                   return Color.orange;
1546         
1547               case 'M':
1548               case 'm':
1549                   return Color.red;
1550                 
1551               case 'N':
1552               case 'n':
1553                   return Color.yellow;
1554                   
1555               case 'O':
1556               case 'o':
1557                   return Color.pink;
1558                 
1559               case 'P':
1560               case 'p':
1561                   return Color.black;
1562                 
1563               case 'Q':
1564               case 'q':
1565                   return Color.blue;
1566         
1567               case 'R':
1568               case 'r':
1569                   return Color.cyan;
1570         
1571               case 'S':
1572               case 's':
1573                   return Color.magenta;
1574                 
1575               case 'T':
1576               case 't':
1577                   return Color.darkGray;
1578                  
1579               case 'U':
1580               case 'u':
1581                   return Color.yellow;
1582                   
1583               case 'V':
1584               case 'v':
1585                   return Color.blue;
1586                  
1587               case 'W':
1588               case 'w':
1589                   return Color.orange;
1590                   
1591               case 'X':
1592               case 'x':
1593                   return Color.magenta;
1594                  
1595               case 'Y':
1596               case 'y':
1597                   return Color.blue;
1598                   
1599               case 'Z':
1600               case 'z':
1601                   return Color.blue;
1602                  
1603         default :
1604                 System.out.println("This is not a interaction");
1605                 return null;
1606                 
1607       }
1608         }
1609 }
1610
1611         
1612