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