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