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