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