revised renderer and annotation model to support different pair-patterns - needs...
[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 drawPseudoNode(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-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     int temp = 0;
322     int x = 0, y = 0;
323     int column = 0;
324     char lastSS;
325     int lastSSX;
326     int iconOffset = 0;
327     boolean validRes = false;
328     boolean validEnd = false;
329     boolean labelAllCols = false;
330     boolean centreColLabels, centreColLabelsDef = av
331             .getCentreColumnLabels();
332     boolean scaleColLabel = false;
333     boolean[] graphGroupDrawn = new boolean[aa.length];
334     int charOffset = 0; // offset for a label
335     float fmWidth, fmScaling = 1f; // scaling for a label to fit it into a
336     // column.
337     Font ofont = g.getFont();
338     // \u03B2 \u03B1
339     for (int i = 0; i < aa.length; i++)
340     {
341       AlignmentAnnotation row = aa[i];
342       Annotation[] row_annotations = row.annotations;
343       if (!row.visible)
344       {
345         continue;
346       }
347       centreColLabels = row.centreColLabels || centreColLabelsDef;
348       labelAllCols = row.showAllColLabels;
349       scaleColLabel = row.scaleColLabel;
350       lastSS = ' ';
351       lastSSX = 0;
352       if (row.graph > 0)
353       {
354         if (row.graphGroup > -1 && graphGroupDrawn[row.graphGroup])
355         {
356           continue;
357         }
358
359         // this is so that we draw the characters below the graph
360         y += row.height;
361
362         if (row.hasText)
363         {
364           iconOffset = charHeight - fm.getDescent();
365           y -= charHeight;
366         }
367       }
368       else if (row.hasText)
369       {
370         iconOffset = charHeight - fm.getDescent();
371
372       }
373       else
374       {
375         iconOffset = 0;
376       }
377
378       if (row.autoCalculated && av.isCalculationInProgress(row))
379       {
380         y += charHeight;
381         usedFaded = true;
382         g.drawImage(fadedImage, 0, y - row.height, imgWidth, y, 0, y
383                 - row.height, imgWidth, y, annotationPanel);
384         g.setColor(Color.black);
385         // g.drawString("Calculating "+aa[i].label+"....",20, y-row.height/2);
386
387         continue;
388       }
389
390       /*
391        * else if (annotationPanel.av.updatingConservation &&
392        * aa[i].label.equals("Conservation")) {
393        * 
394        * y += charHeight; g.drawImage(annotationPanel.fadedImage, 0, y -
395        * row.height, annotationPanel.imgWidth, y, 0, y - row.height,
396        * annotationPanel.imgWidth, y, annotationPanel);
397        * 
398        * g.setColor(Color.black); //
399        * g.drawString("Calculating Conservation.....",20, y-row.height/2);
400        * 
401        * continue; } else if (annotationPanel.av.updatingConservation &&
402        * aa[i].label.equals("Quality")) {
403        * 
404        * y += charHeight; g.drawImage(annotationPanel.fadedImage, 0, y -
405        * row.height, annotationPanel.imgWidth, y, 0, y - row.height,
406        * annotationPanel.imgWidth, y, annotationPanel); g.setColor(Color.black);
407        * // / g.drawString("Calculating Quality....",20, y-row.height/2);
408        * 
409        * continue; }
410        */
411       // first pass sets up state for drawing continuation from left-hand column
412       // of startRes
413       x = (startRes == 0) ? 0 : -1;
414       while (x < endRes - startRes)
415       {
416         if (hasHiddenColumns)
417         {
418           column = columnSelection.adjustForHiddenColumns(startRes + x);
419           if (column > row_annotations.length - 1)
420           {
421             break;
422           }
423         }
424         else
425         {
426           column = startRes + x;
427         }
428
429         if ((row_annotations == null) || (row_annotations.length <= column)
430                 || (row_annotations[column] == null))
431         {
432           validRes = false;
433         }
434         else
435         {
436           validRes = true;
437         }
438         if (x > -1)
439         {
440           if (activeRow == i)
441           {
442             g.setColor(Color.red);
443
444             if (columnSelection != null)
445             {
446               for (int n = 0; n < columnSelection.size(); n++)
447               {
448                 int v = columnSelection.columnAt(n);
449
450                 if (v == column)
451                 {
452                   g.fillRect(x * charWidth, y, charWidth, charHeight);
453                 }
454               }
455             }
456           }
457           if (!row.isValidStruc())
458           {
459             g.setColor(Color.orange);
460             g.fillRect((int) row.getInvalidStrucPos() * charWidth, y,
461                     charWidth, charHeight);
462           }
463           if (validCharWidth
464                   && validRes
465                   && row_annotations[column].displayCharacter != null
466                   && (row_annotations[column].displayCharacter.length() > 0))
467           {
468
469             if (centreColLabels || scaleColLabel)
470             {
471               fmWidth = fm.charsWidth(
472                       row_annotations[column].displayCharacter
473                               .toCharArray(), 0,
474                       row_annotations[column].displayCharacter.length());
475
476               if (scaleColLabel)
477               {
478                 // justify the label and scale to fit in column
479                 if (fmWidth > charWidth)
480                 {
481                   // scale only if the current font isn't already small enough
482                   fmScaling = charWidth;
483                   fmScaling /= fmWidth;
484                   g.setFont(ofont.deriveFont(AffineTransform
485                           .getScaleInstance(fmScaling, 1.0)));
486                   // and update the label's width to reflect the scaling.
487                   fmWidth = charWidth;
488                 }
489               }
490             }
491             else
492             {
493               fmWidth = fm
494                       .charWidth(row_annotations[column].displayCharacter
495                               .charAt(0));
496             }
497             charOffset = (int) ((charWidth - fmWidth) / 2f);
498
499             if (row_annotations[column].colour == null)
500               g.setColor(Color.black);
501             else
502               g.setColor(row_annotations[column].colour);
503
504             if (column == 0 || row.graph > 0)
505             {
506               g.drawString(row_annotations[column].displayCharacter,
507                       (x * charWidth) + charOffset, y + iconOffset);
508             }
509             else if (row_annotations[column - 1] == null
510                     || (labelAllCols
511                             || !row_annotations[column].displayCharacter
512                                     .equals(row_annotations[column - 1].displayCharacter) || (row_annotations[column].displayCharacter
513                             .length() < 2 && row_annotations[column].secondaryStructure == ' ')))
514             {
515                 g.drawString(row_annotations[column].displayCharacter
516                           , x
517                       * charWidth + charOffset, y + iconOffset);
518             }
519             g.setFont(ofont);
520           }
521         }
522         if (row.hasIcons)
523         {
524           char ss = validRes ? row_annotations[column].secondaryStructure
525                   : '-';
526           
527           if (ss == '(')
528           {
529             // distinguish between forward/backward base-pairing
530             if (row_annotations[column].displayCharacter.indexOf(')') > -1)
531             {
532             
533               ss = ')';
534               
535             }
536           }
537            if (ss == '[')
538           {
539             if ((row_annotations[column].displayCharacter.indexOf(']') > -1))
540             {
541                 ss = ']';
542                 
543                 
544             }
545           }
546            if (ss == '{')
547            {
548              // distinguish between forward/backward base-pairing
549              if (row_annotations[column].displayCharacter.indexOf('}') > -1)
550              {
551                ss = '}';
552                
553                
554              }
555            }
556            if (ss == '<')
557            {
558              // distinguish between forward/backward base-pairing
559              if (row_annotations[column].displayCharacter.indexOf('<') > -1)
560              {
561                ss = '>';
562                
563                
564              }
565            }
566            if (ss >=65)
567            {
568                   // System.out.println("ss="+ss);
569              // distinguish between forward/backward base-pairing
570              if (row_annotations[column].displayCharacter.indexOf(ss+32) > -1)
571              {
572             
573                ss = (char) (ss+32);
574               
575                
576              }
577            }
578
579            
580           if (!validRes || (ss != lastSS))
581           {
582                   
583      
584             if (x > -1)
585             {
586         
587                 
588               int nb_annot=x-temp;
589               //System.out.println("\t type :"+lastSS+"\t x :"+x+"\t nbre annot :"+nb_annot);
590               switch (lastSS)
591               {
592               
593               case '$':
594                 drawHelixAnnot(g, row_annotations, lastSSX, x, y, iconOffset, startRes,
595                         column, validRes, validEnd);
596                 break;
597
598               case 'µ':
599                 drawSheetAnnot(g, row_annotations, lastSSX, x, y, iconOffset, startRes,
600                         column, validRes, validEnd);
601                 break;
602
603               case '(': // Stem case for RNA secondary structure
604               case ')': // and opposite direction
605                   
606                   
607                 drawStemAnnot(g, row_annotations, lastSSX, x, y, iconOffset, startRes,
608                         column, validRes, validEnd);
609                 temp=x;
610                 break;
611               case '{':
612               case '}':
613               case '[':
614               case ']':
615               case '>':
616               case '<':
617               case 'A':
618               case 'a':
619               case 'B':
620               case 'b':
621               case 'C':
622               case 'c':
623               case 'D':
624               case 'd':
625               case 'E':
626               case 'e':
627               case 'F':
628               case 'f':
629               case 'G':
630               case 'g':
631               case 'H':
632               case 'h':
633               case 'I':
634               case 'i':
635               case 'J':
636               case 'j':
637               case 'K':
638               case 'k':
639               case 'L':
640               case 'l':
641               case 'M':
642               case 'm':
643               case 'N':
644               case 'n':
645               case 'O':
646               case 'o':
647               case 'P':
648               case 'p':
649               case 'Q':
650               case 'q':
651               case 'R':
652               case 'r':
653               case 'S':
654               case 's':
655               case 'T':
656               case 't':
657               case 'U':
658               case 'u':
659               case 'V':
660               case 'v':
661               case 'W':
662               case 'w':
663               case 'X':
664               case 'x':
665               case 'Y':
666               case 'y':
667               case 'Z':
668               case 'z':
669                   
670                   Color nonCanColor= getNotCanonicalColor(lastSS);
671                   drawPseudoNode(g, nonCanColor, row_annotations, lastSSX, x, y, iconOffset, startRes,
672                           column, validRes, validEnd);
673                   temp=x;
674                   break;
675               default:
676                 g.setColor(Color.gray);
677                 g.fillRect(lastSSX, y + 6 + iconOffset, (x * charWidth)
678                         - lastSSX, 2);
679                 temp=x;
680                 break;
681               }
682             }
683             if (validRes)
684             {
685               lastSS = ss;
686             }
687             else
688             {
689               lastSS = ' ';
690             }
691             if (x > -1)
692             {
693               lastSSX = (x * charWidth);
694             }
695           }
696         }
697         column++;
698         x++;
699       }
700       if (column >= row_annotations.length)
701       {
702         column = row_annotations.length - 1;
703         validEnd = false;
704       }
705       else
706       {
707         validEnd = true;
708       }
709       if ((row_annotations == null) || (row_annotations.length <= column)
710               || (row_annotations[column] == null))
711       {
712         validRes = false;
713       }
714       else
715       {
716         validRes = true;
717       }
718
719       // x ++;
720
721       if (row.hasIcons)
722       {
723         switch (lastSS)
724         {
725         case '$':
726           drawHelixAnnot(g, row_annotations, lastSSX, x, y, iconOffset, startRes,
727                   column, validRes, validEnd);
728           break;
729
730         case 'µ':
731           drawSheetAnnot(g, row_annotations, lastSSX, x, y, iconOffset, startRes,
732                   column, validRes, validEnd);
733           break;
734         case 's':
735         case 'S': // Stem case for RNA secondary structure
736                 
737           drawStemAnnot(g, row_annotations, lastSSX, x, y, iconOffset, startRes,
738                   column, validRes, validEnd);
739           
740           break;
741         case '{':
742         case '}':
743         case '[':
744         case ']':
745         case '>':
746         case '<':
747         case 'A':
748         case 'a':
749         case 'B':
750         case 'b':
751         case 'C':
752         case 'c':
753         case 'D':
754         case 'd':
755         case 'E':
756         case 'e':
757         case 'F':
758         case 'f':
759         case 'G':
760         case 'g':
761         case 'H':
762         case 'h':
763         case 'I':
764         case 'i':
765         case 'J':
766         case 'j':
767         case 'K':
768         case 'k':
769         case 'L':
770         case 'l':
771         case 'M':
772         case 'm':
773         case 'N':
774         case 'n':
775         case 'O':
776         case 'o':
777         case 'P':
778         case 'p':
779         case 'Q':
780         case 'q':
781         case 'R':
782         case 'r':
783         case 'T':
784         case 't':
785         case 'U':
786         case 'u':
787         case 'V':
788         case 'v':
789         case 'W':
790         case 'w':
791         case 'X':
792         case 'x':
793         case 'Y':
794         case 'y':
795         case 'Z':
796         case 'z':
797                 //System.out.println(lastSS);
798                 
799           Color nonCanColor = getNotCanonicalColor(lastSS);
800           drawPseudoNode(g,nonCanColor, row_annotations, lastSSX, x, y, iconOffset, startRes,
801                     column, validRes, validEnd);
802         
803           break;
804           
805         default:
806           drawGlyphLine(g, row_annotations, lastSSX, x, y, iconOffset, startRes,
807                   column, validRes, validEnd);
808           break;
809         }
810       }
811
812       if (row.graph > 0 && row.graphHeight > 0)
813       {
814         if (row.graph == AlignmentAnnotation.LINE_GRAPH)
815         {
816           if (row.graphGroup > -1 && !graphGroupDrawn[row.graphGroup])
817           {
818             float groupmax = -999999, groupmin = 9999999;
819             for (int gg = 0; gg < aa.length; gg++)
820             {
821               if (aa[gg].graphGroup != row.graphGroup)
822               {
823                 continue;
824               }
825
826               if (aa[gg] != row)
827               {
828                 aa[gg].visible = false;
829               }
830
831               if (aa[gg].graphMax > groupmax)
832               {
833                 groupmax = aa[gg].graphMax;
834               }
835               if (aa[gg].graphMin < groupmin)
836               {
837                 groupmin = aa[gg].graphMin;
838               }
839             }
840
841             for (int gg = 0; gg < aa.length; gg++)
842             {
843               if (aa[gg].graphGroup == row.graphGroup)
844               {
845                 drawLineGraph(g, aa[gg], aa[gg].annotations, startRes, endRes, y, groupmin,
846                         groupmax, row.graphHeight);
847               }
848             }
849
850             graphGroupDrawn[row.graphGroup] = true;
851           }
852           else
853           {
854             drawLineGraph(g, row, row_annotations, startRes, endRes, y, row.graphMin,
855                     row.graphMax, row.graphHeight);
856           }
857         }
858         else if (row.graph == AlignmentAnnotation.BAR_GRAPH)
859         {
860           drawBarGraph(g, row, row_annotations, startRes, endRes, row.graphMin,
861                   row.graphMax, y);
862         }
863       }
864
865       if (row.graph > 0 && row.hasText)
866       {
867         y += charHeight;
868       }
869
870       if (row.graph == 0)
871       {
872         y += aa[i].height;
873       }
874     }
875     return !usedFaded;
876   }
877
878   private final Color GLYPHLINE_COLOR = Color.gray;
879
880   private final Color SHEET_COLOUR = Color.green;
881
882   private final Color HELIX_COLOUR = Color.red;
883
884   private final Color STEM_COLOUR = Color.blue;
885   
886   private  Color sdNOTCANONICAL_COLOUR;
887
888   public void drawGlyphLine(Graphics g, Annotation[] row,
889           int lastSSX, int x, int y, int iconOffset, int startRes,
890           int column, boolean validRes, boolean validEnd)
891   {
892     g.setColor(GLYPHLINE_COLOR);
893     g.fillRect(lastSSX, y + 6 + iconOffset, (x * charWidth) - lastSSX, 2);
894   }
895
896   public void drawSheetAnnot(Graphics g, Annotation[] row,
897
898           int lastSSX, int x, int y, int iconOffset, int startRes,
899           int column, boolean validRes, boolean validEnd)
900   {
901     g.setColor(SHEET_COLOUR);
902
903     if (!validEnd || !validRes || row == null || row[column] == null
904             || row[column].secondaryStructure != 'E')
905     {
906       g.fillRect(lastSSX, y + 4 + iconOffset,
907               (x * charWidth) - lastSSX - 4, 7);
908       g.fillPolygon(new int[]
909       { (x * charWidth) - 4, (x * charWidth) - 4, (x * charWidth) },
910               new int[]
911               { y + iconOffset, y + 14 + iconOffset, y + 7 + iconOffset },
912               3);
913     }
914     else
915     {
916       g.fillRect(lastSSX, y + 4 + iconOffset,
917               (x + 1) * charWidth - lastSSX, 7);
918     }
919
920   }
921
922   public void drawHelixAnnot(Graphics g, Annotation[] row, int lastSSX,
923           int x, int y, int iconOffset, int startRes, int column,
924           boolean validRes, boolean validEnd)
925   {
926     g.setColor(HELIX_COLOUR);
927
928     int sCol = (lastSSX / charWidth) + startRes;
929     int x1 = lastSSX;
930     int x2 = (x * charWidth);
931
932     if (MAC)
933     {
934       int ofs = charWidth / 2;
935       // Off by 1 offset when drawing rects and ovals
936       // to offscreen image on the MAC
937       g.fillRoundRect(lastSSX, y + 4 + iconOffset, x2 - x1, 8, 8, 8);
938       if (sCol == 0 || row[sCol - 1] == null
939               || row[sCol - 1].secondaryStructure != 'H')
940       {
941       }
942       else
943       {
944         // g.setColor(Color.orange);
945         g.fillRoundRect(lastSSX, y + 4 + iconOffset, x2 - x1 - ofs + 1, 8,
946                 0, 0);
947       }
948       if (!validRes || row[column] == null
949               || row[column].secondaryStructure != 'H')
950       {
951
952       }
953       else
954       {
955         // g.setColor(Color.magenta);
956         g.fillRoundRect(lastSSX + ofs, y + 4 + iconOffset, x2 - x1 - ofs
957                 + 1, 8, 0, 0);
958
959       }
960
961       return;
962     }
963
964     if (sCol == 0 || row[sCol - 1] == null
965             || row[sCol - 1].secondaryStructure != 'H')
966     {
967       g.fillArc(lastSSX, y + 4 + iconOffset, charWidth, 8, 90, 180);
968       x1 += charWidth / 2;
969     }
970
971     if (!validRes || row[column] == null
972             || row[column].secondaryStructure != 'H')
973     {
974       g.fillArc((x * charWidth) - charWidth, y + 4 + iconOffset, charWidth,
975               8, 270, 180);
976       x2 -= charWidth / 2;
977     }
978
979     g.fillRect(x1, y + 4 + iconOffset, x2 - x1, 8);
980   }
981
982   public void drawLineGraph(Graphics g, AlignmentAnnotation _aa,
983           Annotation[] aa_annotations, int sRes, int eRes, int y,
984           float min, float max, int graphHeight)
985   {
986     if (sRes > aa_annotations.length)
987     {
988       return;
989     }
990
991     int x = 0;
992
993     // Adjustment for fastpaint to left
994     if (eRes < endRes)
995     {
996       eRes++;
997     }
998
999     eRes = Math.min(eRes, aa_annotations.length);
1000
1001     if (sRes == 0)
1002     {
1003       x++;
1004     }
1005
1006     int y1 = y, y2 = y;
1007     float range = max - min;
1008
1009     // //Draw origin
1010     if (min < 0)
1011     {
1012       y2 = y - (int) ((0 - min / range) * graphHeight);
1013     }
1014
1015     g.setColor(Color.gray);
1016     g.drawLine(x - charWidth, y2, (eRes - sRes + 1) * charWidth, y2);
1017
1018     eRes = Math.min(eRes, aa_annotations.length);
1019
1020     int column;
1021     int aaMax = aa_annotations.length - 1;
1022
1023     while (x < eRes - sRes)
1024     {
1025       column = sRes + x;
1026       if (hasHiddenColumns)
1027       {
1028         column = columnSelection.adjustForHiddenColumns(column);
1029       }
1030
1031       if (column > aaMax)
1032       {
1033         break;
1034       }
1035
1036       if (aa_annotations[column] == null
1037               || aa_annotations[column - 1] == null)
1038       {
1039         x++;
1040         continue;
1041       }
1042
1043       if (aa_annotations[column].colour == null)
1044         g.setColor(Color.black);
1045       else
1046         g.setColor(aa_annotations[column].colour);
1047
1048       y1 = y
1049               - (int) (((aa_annotations[column - 1].value - min) / range) * graphHeight);
1050       y2 = y
1051               - (int) (((aa_annotations[column].value - min) / range) * graphHeight);
1052
1053       g.drawLine(x * charWidth - charWidth / 2, y1, x * charWidth
1054               + charWidth / 2, y2);
1055       x++;
1056     }
1057
1058     if (_aa.threshold != null)
1059     {
1060       g.setColor(_aa.threshold.colour);
1061       Graphics2D g2 = (Graphics2D) g;
1062       g2.setStroke(new BasicStroke(1, BasicStroke.CAP_SQUARE,
1063               BasicStroke.JOIN_ROUND, 3f, new float[]
1064               { 5f, 3f }, 0f));
1065
1066       y2 = (int) (y - ((_aa.threshold.value - min) / range) * graphHeight);
1067       g.drawLine(0, y2, (eRes - sRes) * charWidth, y2);
1068       g2.setStroke(new BasicStroke());
1069     }
1070   }
1071
1072   public void drawBarGraph(Graphics g, AlignmentAnnotation _aa,
1073           Annotation[] aa_annotations, int sRes, int eRes, float min,
1074           float max, int y)
1075   {
1076     if (sRes > aa_annotations.length)
1077     {
1078       return;
1079     }
1080     Font ofont = g.getFont();
1081     eRes = Math.min(eRes, aa_annotations.length);
1082
1083     int x = 0, y1 = y, y2 = y;
1084
1085     float range = max - min;
1086
1087     if (min < 0)
1088     {
1089       y2 = y - (int) ((0 - min / (range)) * _aa.graphHeight);
1090     }
1091
1092     g.setColor(Color.gray);
1093
1094     g.drawLine(x, y2, (eRes - sRes) * charWidth, y2);
1095
1096     int column;
1097     int aaMax = aa_annotations.length - 1;
1098     boolean renderHistogram = true, renderProfile = true, normaliseProfile = false;
1099     // if (aa.autoCalculated && aa.label.startsWith("Consensus"))
1100     {
1101       // TODO: generalise this to have render styles for consensus/profile data
1102       if (_aa.groupRef != null)
1103       {
1104         renderHistogram = _aa.groupRef.isShowConsensusHistogram();
1105         renderProfile = _aa.groupRef.isShowSequenceLogo();
1106         normaliseProfile = _aa.groupRef.isNormaliseSequenceLogo();
1107       }
1108       else
1109       {
1110         renderHistogram = av_renderHistogram;
1111         renderProfile = av_renderProfile;
1112         normaliseProfile = av_normaliseProfile;
1113       }
1114     }
1115     while (x < eRes - sRes)
1116     {
1117       column = sRes + x;
1118       if (hasHiddenColumns)
1119       {
1120         column = columnSelection.adjustForHiddenColumns(column);
1121       }
1122
1123       if (column > aaMax)
1124       {
1125         break;
1126       }
1127
1128       if (aa_annotations[column] == null)
1129       {
1130         x++;
1131         continue;
1132       }
1133       if (aa_annotations[column].colour == null)
1134         g.setColor(Color.black);
1135       else
1136         g.setColor(aa_annotations[column].colour);
1137
1138       y1 = y
1139               - (int) (((aa_annotations[column].value - min) / (range)) * _aa.graphHeight);
1140
1141       if (renderHistogram)
1142       {
1143         if (y1 - y2 > 0)
1144         {
1145           g.fillRect(x * charWidth, y2, charWidth, y1 - y2);
1146         }
1147         else
1148         {
1149           g.fillRect(x * charWidth, y1, charWidth, y2 - y1);
1150         }
1151       }
1152       // draw profile if available
1153       if (renderProfile)
1154       {
1155
1156         int profl[] = getProfileFor(_aa, column);
1157         // just try to draw the logo if profl is not null
1158         if (profl != null && profl[1] != 0)
1159         {
1160           float ht = normaliseProfile ? y - _aa.graphHeight : y1;
1161           double htn = normaliseProfile ? _aa.graphHeight : (y2 - y1);// aa.graphHeight;
1162           double hght;
1163           float wdth;
1164           double ht2 = 0;
1165           char[] dc;
1166
1167           /**
1168            * profl.length == 74 indicates that the profile of a secondary
1169            * structure conservation row was accesed. Therefore dc gets length 2,
1170            * to have space for a basepair instead of just a single nucleotide
1171            */
1172           if (profl.length == 74)
1173           {
1174             dc = new char[2];
1175           }
1176           else
1177           {
1178             dc = new char[1];
1179           }
1180           LineMetrics lm = g.getFontMetrics(ofont).getLineMetrics("Q", g);
1181           double scale = 1f / (normaliseProfile ? profl[1] : 100f);
1182           float ofontHeight = 1f / lm.getAscent();// magnify to fill box
1183           double scl = 0.0;
1184           for (int c = 2; c < profl[0];)
1185           {
1186             dc[0] = (char) profl[c++];
1187
1188             if (_aa.label.startsWith("StrucConsensus"))
1189             {
1190               dc[1] = (char) profl[c++];
1191             }
1192
1193             wdth = charWidth;
1194             wdth /= fm.charsWidth(dc, 0, dc.length);
1195
1196             ht += scl;
1197             {
1198               scl = htn * scale * profl[c++];
1199               lm = ofont.getLineMetrics(dc, 0, 1, g.getFontMetrics()
1200                       .getFontRenderContext());
1201               g.setFont(ofont.deriveFont(AffineTransform.getScaleInstance(
1202                       wdth, scl / lm.getAscent())));
1203               lm = g.getFontMetrics().getLineMetrics(dc, 0, 1, g);
1204
1205               // Debug - render boxes around characters
1206               // g.setColor(Color.red);
1207               // g.drawRect(x*av.charWidth, (int)ht, av.charWidth,
1208               // (int)(scl));
1209               // g.setColor(profcolour.findColour(dc[0]).darker());
1210               g.setColor(profcolour.findColour(dc[0], column, null));
1211
1212               hght = (ht + (scl - lm.getDescent() - lm.getBaselineOffsets()[lm
1213                       .getBaselineIndex()]));
1214
1215               g.drawChars(dc, 0, dc.length, x * charWidth, (int) hght);
1216             }
1217           }
1218           g.setFont(ofont);
1219         }
1220       }
1221       x++;
1222     }
1223     if (_aa.threshold != null)
1224     {
1225       g.setColor(_aa.threshold.colour);
1226       Graphics2D g2 = (Graphics2D) g;
1227       g2.setStroke(new BasicStroke(1, BasicStroke.CAP_SQUARE,
1228               BasicStroke.JOIN_ROUND, 3f, new float[]
1229               { 5f, 3f }, 0f));
1230
1231       y2 = (int) (y - ((_aa.threshold.value - min) / range)
1232               * _aa.graphHeight);
1233       g.drawLine(0, y2, (eRes - sRes) * charWidth, y2);
1234       g2.setStroke(new BasicStroke());
1235     }
1236   }
1237
1238   // used by overview window
1239   public void drawGraph(Graphics g, AlignmentAnnotation _aa,
1240           Annotation[] aa_annotations, int width, int y, int sRes, int eRes)
1241   {
1242     eRes = Math.min(eRes, aa_annotations.length);
1243     g.setColor(Color.white);
1244     g.fillRect(0, 0, width, y);
1245     g.setColor(new Color(0, 0, 180));
1246
1247     int x = 0, height;
1248
1249     for (int j = sRes; j < eRes; j++)
1250     {
1251       if (aa_annotations[j] != null)
1252       {
1253         if (aa_annotations[j].colour == null)
1254           g.setColor(Color.black);
1255         else
1256           g.setColor(aa_annotations[j].colour);
1257
1258         height = (int) ((aa_annotations[j].value / _aa.graphMax) * y);
1259         if (height > y)
1260         {
1261           height = y;
1262         }
1263
1264         g.fillRect(x, y - height, charWidth, height);
1265       }
1266       x += charWidth;
1267     }
1268   }
1269
1270   
1271   Color getNotCanonicalColor(char lastss)
1272         {
1273           switch (lastss)
1274       {
1275           case '{':  
1276               case '}':
1277                   return new Color(255,125,5);
1278                  
1279               case '[':
1280               case ']':
1281                   return new Color(245,115,10);
1282                  
1283               case '>':
1284               case '<':
1285                   return new Color(235,135,15);
1286                   
1287               case 'A':
1288               case 'a':
1289                   return new Color(225,105,20);
1290                 
1291               case 'B':
1292               case 'b':
1293                   return new Color(215,145,30);
1294                   
1295               case 'C':
1296               case 'c':
1297                   return new Color(205,95,35);
1298                  
1299               case 'D':
1300               case 'd':
1301                   return new Color(195,155,45);
1302                   
1303               case 'E':
1304               case 'e':
1305                   return new Color(185,85,55);
1306                  
1307               case 'F':
1308               case 'f':
1309                   return new Color(175,165,65);
1310                  
1311               case 'G':
1312               case 'g':
1313                   return new Color(170,75,75);
1314                 
1315               case 'H':
1316               case 'h':
1317                   return new Color(160,175,85);
1318                   
1319               case 'I':
1320               case 'i':
1321                   return new Color(150,65,95);
1322                  
1323               case 'J':
1324               case 'j':
1325                   return new Color(140,185,105);
1326                   
1327               case 'K':
1328               case 'k':
1329                   return new Color(130,55,110);
1330                 
1331               case 'L':
1332               case 'l':
1333                   return new Color(120,195,120);
1334         
1335               case 'M':
1336               case 'm':
1337                   return new Color(110,45,130);
1338                 
1339               case 'N':
1340               case 'n':
1341                   return new Color(100,205,140);
1342                   
1343               case 'O':
1344               case 'o':
1345                   return new Color(90,35,150);
1346                 
1347               case 'P':
1348               case 'p':
1349                   return new Color(85,215,160);
1350                 
1351               case 'Q':
1352               case 'q':
1353                   return new Color(75,25,170);
1354         
1355               case 'R':
1356               case 'r':
1357                   return new Color(65,225,180);
1358         
1359               case 'S':
1360               case 's':
1361                   return new Color(55,15,185);
1362                 
1363               case 'T':
1364               case 't':
1365                   return new Color(45,235,195);
1366                  
1367               case 'U':
1368               case 'u':
1369                   return new Color(35,5,205);
1370                   
1371               case 'V':
1372               case 'v':
1373                   return new Color(25,245,215);
1374                  
1375               case 'W':
1376               case 'w':
1377                   return new Color(15,0,225);
1378                   
1379               case 'X':
1380               case 'x':
1381                   return new Color(10,255,235);
1382                  
1383               case 'Y':
1384               case 'y':
1385                   return new Color(5,150,245);
1386                   
1387               case 'Z':
1388               case 'z':
1389                   return new Color(0,80,255);
1390                  
1391         default :
1392                 System.out.println("This is not a interaction : "+lastss);
1393                 return null;
1394                 
1395       }
1396         }
1397 }
1398
1399         
1400