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