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