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