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