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