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