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