JAL-1432 updated copyright notices
[jalview.git] / src / jalview / renderer / AnnotationRenderer.java
1 /*
2  * Jalview - A Sequence Alignment Editor and Viewer (Version 2.8.0b1)
3  * Copyright (C) 2014 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 of the License, or (at your option) any later version.
10  *  
11  * Jalview is distributed in the hope that it will be useful, but 
12  * WITHOUT ANY WARRANTY; without even the implied warranty 
13  * of MERCHANTABILITY or FITNESS FOR A PARTICULAR 
14  * PURPOSE.  See the GNU General Public License for more details.
15  * 
16  * You should have received a copy of the GNU General Public License along with Jalview.  If not, see <http://www.gnu.org/licenses/>.
17  * The Jalview Authors are detailed in the 'AUTHORS' file.
18  */
19 package jalview.renderer;
20
21 import jalview.analysis.AAFrequency;
22 import jalview.analysis.StructureFrequency;
23 import jalview.api.AlignViewportI;
24 import jalview.datamodel.AlignmentAnnotation;
25 import jalview.datamodel.Annotation;
26 import jalview.datamodel.ColumnSelection;
27 import jalview.schemes.ColourSchemeI;
28
29 import java.awt.BasicStroke;
30 import java.awt.Color;
31 import java.awt.Font;
32 import java.awt.FontMetrics;
33 import java.awt.Graphics;
34 import java.awt.Graphics2D;
35 import java.awt.Image;
36 import java.awt.font.LineMetrics;
37 import java.awt.geom.AffineTransform;
38 import java.awt.image.ImageObserver;
39 import java.util.BitSet;
40 import java.util.Hashtable;
41
42 import com.stevesoft.pat.Regex;
43
44 public class AnnotationRenderer
45 {
46   /**
47    * flag indicating if timing and redraw parameter info should be output
48    */
49   private final boolean debugRedraw;
50
51   public AnnotationRenderer()
52   {
53     this(false);
54   }
55   /**
56    * Create a new annotation Renderer
57    * @param debugRedraw flag indicating if timing and redraw parameter info should be output
58    */
59   public AnnotationRenderer(boolean debugRedraw)
60   {
61     this.debugRedraw=debugRedraw;
62   }
63
64   public void drawStemAnnot(Graphics g, Annotation[] row_annotations,
65           int lastSSX, int x, int y, int iconOffset, int startRes,
66           int column, boolean validRes, boolean validEnd)
67   {
68     g.setColor(STEM_COLOUR);
69     int sCol = (lastSSX / charWidth) + startRes;
70     int x1 = lastSSX;
71     int x2 = (x * charWidth);
72     Regex closeparen = new Regex("(\\))");
73
74     String dc = (column == 0 || row_annotations[column - 1] == null) ? ""
75             : row_annotations[column - 1].displayCharacter;
76
77     boolean diffupstream = sCol == 0 || row_annotations[sCol - 1] == null
78             || !dc.equals(row_annotations[sCol - 1].displayCharacter);
79     boolean diffdownstream = !validRes || !validEnd
80             || row_annotations[column] == null
81             || !dc.equals(row_annotations[column].displayCharacter);
82     // System.out.println("Column "+column+" diff up: "+diffupstream+" down:"+diffdownstream);
83     // If a closing base pair half of the stem, display a backward arrow
84     if (column > 0 && closeparen.search(dc))
85     {
86       if (diffupstream)
87       // if (validRes && column>1 && row_annotations[column-2]!=null &&
88       // dc.equals(row_annotations[column-2].displayCharacter))
89       {
90         g.fillPolygon(new int[]
91         { lastSSX + 5, lastSSX + 5, lastSSX }, new int[]
92         { y + iconOffset, y + 14 + iconOffset, y + 8 + iconOffset }, 3);
93         x1 += 5;
94       }
95       if (diffdownstream)
96       {
97         x2 -= 1;
98       }
99     }
100     else
101     {
102       // display a forward arrow
103       if (diffdownstream)
104       {
105         g.fillPolygon(new int[]
106         { x2 - 5, x2 - 5, x2 }, new int[]
107         { y + iconOffset, y + 14 + iconOffset, y + 8 + iconOffset }, 3);
108         x2 -= 5;
109       }
110       if (diffupstream)
111       {
112         x1 += 1;
113       }
114     }
115     // draw arrow body
116     g.fillRect(x1, y + 4 + iconOffset, x2 - x1, 7);
117   }
118
119   private int charWidth, endRes, charHeight;
120
121   private boolean validCharWidth, hasHiddenColumns;
122
123   private FontMetrics fm;
124
125   private final boolean MAC = new jalview.util.Platform().isAMac();
126
127   boolean av_renderHistogram = true, av_renderProfile = true,
128           av_normaliseProfile = false;
129
130   ColourSchemeI profcolour = null;
131
132   private ColumnSelection columnSelection;
133
134   private Hashtable[] hconsensus;
135
136   private Hashtable[] hStrucConsensus;
137
138   private boolean av_ignoreGapsConsensus;
139
140   /**
141    * attributes set from AwtRenderPanelI
142    */
143   /**
144    * old image used when data is currently being calculated and cannot be
145    * rendered
146    */
147   private Image fadedImage;
148
149   /**
150    * panel being rendered into
151    */
152   private ImageObserver annotationPanel;
153
154   /**
155    * width of image to render in panel
156    */
157   private int imgWidth;
158   /**
159    * offset to beginning of visible area
160    */
161   private int sOffset;
162   /**
163    * offset to end of visible area
164    */
165   private int visHeight;
166   /**
167    * indicate if the renderer should only render the visible portion of the annotation given the current view settings
168    */
169   private boolean useClip=true;
170   /**
171    * master flag indicating if renderer should ever try to clip. not enabled for jalview 2.8.1 
172    */
173   private boolean canClip=false;
174   
175   // public void updateFromAnnotationPanel(FontMetrics annotFM, AlignViewportI
176   // av)
177   public void updateFromAwtRenderPanel(AwtRenderPanelI annotPanel,
178           AlignViewportI av)
179   {
180     fm = annotPanel.getFontMetrics();
181     annotationPanel = annotPanel;
182     fadedImage = annotPanel.getFadedImage();
183     imgWidth = annotPanel.getFadedImageWidth();
184     // visible area for rendering
185     int[] bounds=annotPanel.getVisibleVRange();
186     if (bounds!=null)
187     {
188       sOffset = bounds[0];
189       visHeight = bounds[1];
190       if (visHeight==0)
191       {
192         useClip=false;
193       } else {
194         useClip=canClip;
195       }
196     } else {
197       useClip=false;
198     }
199
200     updateFromAlignViewport(av);
201   }
202
203   public void updateFromAlignViewport(AlignViewportI av)
204   {
205     charWidth = av.getCharWidth();
206     endRes = av.getEndRes();
207     charHeight = av.getCharHeight();
208     hasHiddenColumns = av.hasHiddenColumns();
209     validCharWidth = av.isValidCharWidth();
210     av_renderHistogram = av.isShowConsensusHistogram();
211     av_renderProfile = av.isShowSequenceLogo();
212     av_normaliseProfile = av.isNormaliseSequenceLogo();
213     profcolour = av.getGlobalColourScheme();
214     if (profcolour == null)
215     {
216       // Set the default colour for sequence logo if the alignnent has no
217       // colourscheme set
218       profcolour = av.getAlignment().isNucleotide() ? new jalview.schemes.NucleotideColourScheme()
219               : new jalview.schemes.ZappoColourScheme();
220     }
221     columnSelection = av.getColumnSelection();
222     hconsensus = av.getSequenceConsensusHash();// hconsensus;
223     hStrucConsensus = av.getRnaStructureConsensusHash(); // hStrucConsensus;
224     av_ignoreGapsConsensus = av.getIgnoreGapsConsensus();
225   }
226
227   public int[] getProfileFor(AlignmentAnnotation aa, int column)
228   {
229     // TODO : consider refactoring the global alignment calculation
230     // properties/rendering attributes as a global 'alignment group' which holds
231     // all vis settings for the alignment as a whole rather than a subset
232     //
233     if (aa.autoCalculated && aa.label.startsWith("Consensus"))
234     {
235       if (aa.groupRef != null && aa.groupRef.consensusData != null
236               && aa.groupRef.isShowSequenceLogo())
237       {
238         return AAFrequency.extractProfile(
239                 aa.groupRef.consensusData[column],
240                 aa.groupRef.getIgnoreGapsConsensus());
241       }
242       // TODO extend annotation row to enable dynamic and static profile data to
243       // be stored
244       if (aa.groupRef == null && aa.sequenceRef == null)
245       {
246         return AAFrequency.extractProfile(hconsensus[column],
247                 av_ignoreGapsConsensus);
248       }
249     }
250     else
251     {
252       if (aa.autoCalculated && aa.label.startsWith("StrucConsensus"))
253       {
254         // TODO implement group structure consensus
255         /*
256          * if (aa.groupRef != null && aa.groupRef.consensusData != null &&
257          * aa.groupRef.isShowSequenceLogo()) { //TODO check what happens for
258          * group selections return StructureFrequency.extractProfile(
259          * aa.groupRef.consensusData[column], aa.groupRef
260          * .getIgnoreGapsConsensus()); }
261          */
262         // TODO extend annotation row to enable dynamic and static profile data
263         // to
264         // be stored
265         if (aa.groupRef == null && aa.sequenceRef == null
266                 && hStrucConsensus != null
267                 && hStrucConsensus.length > column)
268         {
269           return StructureFrequency.extractProfile(hStrucConsensus[column],
270                   av_ignoreGapsConsensus);
271         }
272       }
273     }
274     return null;
275   }
276
277   /**
278    * Render the annotation rows associated with an alignment.
279    * 
280    * @param annotPanel
281    *          container frame
282    * @param av
283    *          data and view settings to render
284    * @param g
285    *          destination for graphics
286    * @param activeRow
287    *          row where a mouse event occured (or -1)
288    * @param startRes
289    *          first column that will be drawn
290    * @param endRes
291    *          last column that will be drawn
292    * @return true if the fadedImage was used for any alignment annotation rows
293    *         currently being calculated
294    */
295   public boolean drawComponent(AwtRenderPanelI annotPanel,
296           AlignViewportI av, Graphics g, int activeRow, int startRes,
297           int endRes)
298   {
299     long stime=System.currentTimeMillis();
300     boolean usedFaded = false;
301     // NOTES:
302     // AnnotationPanel needs to implement: ImageObserver, access to
303     // AlignViewport
304     updateFromAwtRenderPanel(annotPanel, av);
305     fm = g.getFontMetrics();
306     AlignmentAnnotation[] aa = av.getAlignment().getAlignmentAnnotation();
307     if (aa==null)
308     {
309       return false;
310     }
311     int x = 0, y = 0;
312     int column = 0;
313     char lastSS;
314     int lastSSX;
315     int iconOffset = 0;
316     boolean validRes = false;
317     boolean validEnd = false;
318     boolean labelAllCols = false;
319     boolean centreColLabels, centreColLabelsDef = av
320             .getCentreColumnLabels();
321     boolean scaleColLabel = false;
322     AlignmentAnnotation consensusAnnot=av.getAlignmentConsensusAnnotation(),structConsensusAnnot=av.getAlignmentStrucConsensusAnnotation();
323     boolean renderHistogram = true, renderProfile = true, normaliseProfile = false;
324
325     BitSet graphGroupDrawn = new BitSet();
326     int charOffset = 0; // offset for a label
327     float fmWidth, fmScaling = 1f; // scaling for a label to fit it into a
328     // column.
329     Font ofont = g.getFont();
330     // \u03B2 \u03B1
331     // debug ints
332     int yfrom=0,f_i=0,yto=0,f_to=0;
333     boolean clipst=false,clipend=false;
334     for (int i = 0; i < aa.length; i++)
335     {
336       AlignmentAnnotation row = aa[i];
337       {
338         // check if this is a consensus annotation row and set the display settings appropriately
339         // TODO: generalise this to have render styles for consensus/profile
340         // data
341         if (row.groupRef != null && row == row.groupRef.getConsensus())
342         {
343           renderHistogram = row.groupRef.isShowConsensusHistogram();
344           renderProfile = row.groupRef.isShowSequenceLogo();
345           normaliseProfile = row.groupRef.isNormaliseSequenceLogo();
346         }
347         else if (row == consensusAnnot || row == structConsensusAnnot)
348         {
349           renderHistogram = av_renderHistogram;
350           renderProfile = av_renderProfile;
351           normaliseProfile = av_normaliseProfile;
352         } else {
353           renderHistogram = true;
354           // don't need to set render/normaliseProfile since they are not currently used in any other annotation track renderer
355         }
356       }
357       Annotation[] row_annotations = row.annotations;
358       if (!row.visible)
359       {
360         continue;
361       }
362       centreColLabels = row.centreColLabels || centreColLabelsDef;
363       labelAllCols = row.showAllColLabels;
364       scaleColLabel = row.scaleColLabel;
365       lastSS = ' ';
366       lastSSX = 0;
367       
368       if (!useClip || ((y-charHeight)<visHeight && (y+row.height+charHeight*2)>=sOffset)) 
369       {// if_in_visible_region
370         if (!clipst)
371         {
372           clipst=true;
373           yfrom=y;
374           f_i=i;
375         }
376         yto = y;
377         f_to=i;
378       if (row.graph > 0)
379       {
380         if (row.graphGroup > -1 && graphGroupDrawn.get(row.graphGroup)) {
381           continue;
382         }
383
384         // this is so that we draw the characters below the graph
385         y += row.height;
386
387         if (row.hasText)
388         {
389           iconOffset = charHeight - fm.getDescent();
390           y -= charHeight;
391         }
392       }
393       else if (row.hasText)
394       {
395         iconOffset = charHeight - fm.getDescent();
396
397       }
398       else
399       {
400         iconOffset = 0;
401       }
402
403       if (row.autoCalculated && av.isCalculationInProgress(row))
404       {
405         y += charHeight;
406         usedFaded = true;
407         g.drawImage(fadedImage, 0, y - row.height, imgWidth, y, 0, y
408                 - row.height, imgWidth, y, annotationPanel);
409         g.setColor(Color.black);
410         // g.drawString("Calculating "+aa[i].label+"....",20, y-row.height/2);
411
412         continue;
413       }
414
415       /*
416        * else if (annotationPanel.av.updatingConservation &&
417        * aa[i].label.equals("Conservation")) {
418        * 
419        * y += charHeight; g.drawImage(annotationPanel.fadedImage, 0, y -
420        * row.height, annotationPanel.imgWidth, y, 0, y - row.height,
421        * annotationPanel.imgWidth, y, annotationPanel);
422        * 
423        * g.setColor(Color.black); //
424        * g.drawString("Calculating Conservation.....",20, y-row.height/2);
425        * 
426        * continue; } else if (annotationPanel.av.updatingConservation &&
427        * aa[i].label.equals("Quality")) {
428        * 
429        * y += charHeight; g.drawImage(annotationPanel.fadedImage, 0, y -
430        * row.height, annotationPanel.imgWidth, y, 0, y - row.height,
431        * annotationPanel.imgWidth, y, annotationPanel); g.setColor(Color.black);
432        * // / g.drawString("Calculating Quality....",20, y-row.height/2);
433        * 
434        * continue; }
435        */
436       // first pass sets up state for drawing continuation from left-hand column
437       // of startRes
438       x = (startRes == 0) ? 0 : -1;
439       while (x < endRes - startRes)
440       {
441         if (hasHiddenColumns)
442         {
443           column = columnSelection.adjustForHiddenColumns(startRes + x);
444           if (column > row_annotations.length - 1)
445           {
446             break;
447           }
448         }
449         else
450         {
451           column = startRes + x;
452         }
453
454         if ((row_annotations == null) || (row_annotations.length <= column)
455                 || (row_annotations[column] == null))
456         {
457           validRes = false;
458         }
459         else
460         {
461           validRes = true;
462         }
463         if (x > -1)
464         {
465           if (activeRow == i)
466           {
467             g.setColor(Color.red);
468
469             if (columnSelection != null)
470             {
471               for (int n = 0; n < columnSelection.size(); n++)
472               {
473                 int v = columnSelection.columnAt(n);
474
475                 if (v == column)
476                 {
477                   g.fillRect(x * charWidth, y, charWidth, charHeight);
478                 }
479               }
480             }
481           }
482           if (!row.isValidStruc())
483           {
484             g.setColor(Color.orange);
485             g.fillRect((int) row.getInvalidStrucPos() * charWidth, y,
486                     charWidth, charHeight);
487           }
488           if (validCharWidth
489                   && validRes
490                   && row_annotations[column].displayCharacter != null
491                   && (row_annotations[column].displayCharacter.length() > 0))
492           {
493
494             if (centreColLabels || scaleColLabel)
495             {
496               fmWidth = fm.charsWidth(
497                       row_annotations[column].displayCharacter
498                               .toCharArray(), 0,
499                       row_annotations[column].displayCharacter.length());
500
501               if (scaleColLabel)
502               {
503                 // justify the label and scale to fit in column
504                 if (fmWidth > charWidth)
505                 {
506                   // scale only if the current font isn't already small enough
507                   fmScaling = charWidth;
508                   fmScaling /= fmWidth;
509                   g.setFont(ofont.deriveFont(AffineTransform
510                           .getScaleInstance(fmScaling, 1.0)));
511                   // and update the label's width to reflect the scaling.
512                   fmWidth = charWidth;
513                 }
514               }
515             }
516             else
517             {
518               fmWidth = fm
519                       .charWidth(row_annotations[column].displayCharacter
520                               .charAt(0));
521             }
522             charOffset = (int) ((charWidth - fmWidth) / 2f);
523
524             if (row_annotations[column].colour == null)
525               g.setColor(Color.black);
526             else
527               g.setColor(row_annotations[column].colour);
528
529             if (column == 0 || row.graph > 0)
530             {
531               g.drawString(row_annotations[column].displayCharacter,
532                       (x * charWidth) + charOffset, y + iconOffset);
533             }
534             else if (row_annotations[column - 1] == null
535                     || (labelAllCols
536                             || !row_annotations[column].displayCharacter
537                                     .equals(row_annotations[column - 1].displayCharacter) || (row_annotations[column].displayCharacter
538                             .length() < 2 && row_annotations[column].secondaryStructure == ' ')))
539             {
540               g.drawString(row_annotations[column].displayCharacter, x
541                       * charWidth + charOffset, y + iconOffset);
542             }
543             g.setFont(ofont);
544           }
545         }
546         if (row.hasIcons)
547         {
548           char ss = validRes ? row_annotations[column].secondaryStructure
549                   : ' ';
550           if (ss == 'S')
551           {
552             // distinguish between forward/backward base-pairing
553             if (row_annotations[column].displayCharacter.indexOf(')') > -1)
554             {
555               ss = 's';
556             }
557           }
558           if (!validRes || (ss != lastSS))
559           {
560             if (x > -1)
561             {
562               switch (lastSS)
563               {
564               case 'H':
565                 drawHelixAnnot(g, row_annotations, lastSSX, x, y,
566                         iconOffset, startRes, column, validRes, validEnd);
567                 break;
568
569               case 'E':
570                 drawSheetAnnot(g, row_annotations, lastSSX, x, y,
571                         iconOffset, startRes, column, validRes, validEnd);
572                 break;
573
574               case 'S': // Stem case for RNA secondary structure
575               case 's': // and opposite direction
576                 drawStemAnnot(g, row_annotations, lastSSX, x, y,
577                         iconOffset, startRes, column, validRes, validEnd);
578                 break;
579
580               default:
581                 g.setColor(Color.gray);
582                 g.fillRect(lastSSX, y + 6 + iconOffset, (x * charWidth)
583                         - lastSSX, 2);
584
585                 break;
586               }
587             }
588             if (validRes)
589             {
590               lastSS = ss;
591             }
592             else
593             {
594               lastSS = ' ';
595             }
596             if (x > -1)
597             {
598               lastSSX = (x * charWidth);
599             }
600           }
601         }
602         column++;
603         x++;
604       }
605       if (column >= row_annotations.length)
606       {
607         column = row_annotations.length - 1;
608         validEnd = false;
609       }
610       else
611       {
612         validEnd = true;
613       }
614       if ((row_annotations == null) || (row_annotations.length <= column)
615               || (row_annotations[column] == null))
616       {
617         validRes = false;
618       }
619       else
620       {
621         validRes = true;
622       }
623
624       // x ++;
625
626       if (row.hasIcons)
627       {
628         switch (lastSS)
629         {
630         case 'H':
631           drawHelixAnnot(g, row_annotations, lastSSX, x, y, iconOffset,
632                   startRes, column, validRes, validEnd);
633           break;
634
635         case 'E':
636           drawSheetAnnot(g, row_annotations, lastSSX, x, y, iconOffset,
637                   startRes, column, validRes, validEnd);
638           break;
639         case 's':
640         case 'S': // Stem case for RNA secondary structure
641           drawStemAnnot(g, row_annotations, lastSSX, x, y, iconOffset,
642                   startRes, column, validRes, validEnd);
643           break;
644         default:
645           drawGlyphLine(g, row_annotations, lastSSX, x, y, iconOffset,
646                   startRes, column, validRes, validEnd);
647           break;
648         }
649       }
650
651       if (row.graph > 0 && row.graphHeight > 0)
652       {
653         if (row.graph == AlignmentAnnotation.LINE_GRAPH)
654         {
655           if (row.graphGroup > -1 && !graphGroupDrawn.get(row.graphGroup))
656           {
657             // TODO: JAL-1291 revise rendering model so the graphGroup map is computed efficiently for all visible labels
658             float groupmax = -999999, groupmin = 9999999;
659             for (int gg = 0; gg < aa.length; gg++)
660             {
661               if (aa[gg].graphGroup != row.graphGroup)
662               {
663                 continue;
664               }
665
666               if (aa[gg] != row)
667               {
668                 aa[gg].visible = false;
669               }
670               if (aa[gg].graphMax > groupmax)
671               {
672                 groupmax = aa[gg].graphMax;
673               }
674               if (aa[gg].graphMin < groupmin)
675               {
676                 groupmin = aa[gg].graphMin;
677               }
678             }
679
680             for (int gg = 0; gg < aa.length; gg++)
681             {
682               if (aa[gg].graphGroup == row.graphGroup)
683               {
684                 drawLineGraph(g, aa[gg], aa[gg].annotations, startRes,
685                         endRes, y, groupmin, groupmax, row.graphHeight);
686               }
687             }
688
689             graphGroupDrawn.set(row.graphGroup);
690           }
691           else
692           {
693             drawLineGraph(g, row, row_annotations, startRes, endRes, y,
694                     row.graphMin, row.graphMax, row.graphHeight);
695           }
696         }
697         else if (row.graph == AlignmentAnnotation.BAR_GRAPH)
698         {
699           drawBarGraph(g, row, row_annotations, startRes, endRes,
700                   row.graphMin, row.graphMax, y, renderHistogram,renderProfile,normaliseProfile);
701         }
702       }
703     } else {
704       if (clipst && !clipend)
705       {
706         clipend = true;
707       }
708     }// end if_in_visible_region
709       if (row.graph > 0 && row.hasText)
710       {
711         y += charHeight;
712       }
713
714       if (row.graph == 0)
715       {
716         y += aa[i].height;
717       }
718     }
719     if (debugRedraw)
720     {
721       if (canClip)
722       {
723         if (clipst)
724         {
725           System.err.println("Start clip at : " + yfrom + " (index " + f_i
726                   + ")");
727         }
728         if (clipend)
729         {
730           System.err.println("End clip at : " + yto + " (index " + f_to
731                   + ")");
732         }
733       }
734       ;
735       System.err.println("Annotation Rendering time:"
736               + (System.currentTimeMillis() - stime));
737     }
738     ;
739
740     return !usedFaded;
741   }
742
743   private final Color GLYPHLINE_COLOR = Color.gray;
744
745   private final Color SHEET_COLOUR = Color.green;
746
747   private final Color HELIX_COLOUR = Color.red;
748
749   private final Color STEM_COLOUR = Color.blue;
750
751   public void drawGlyphLine(Graphics g, Annotation[] row, int lastSSX,
752           int x, int y, int iconOffset, int startRes, int column,
753           boolean validRes, boolean validEnd)
754   {
755     g.setColor(GLYPHLINE_COLOR);
756     g.fillRect(lastSSX, y + 6 + iconOffset, (x * charWidth) - lastSSX, 2);
757   }
758
759   public void drawSheetAnnot(Graphics g, Annotation[] row, int lastSSX,
760           int x, int y, int iconOffset, int startRes, int column,
761           boolean validRes, boolean validEnd)
762   {
763     g.setColor(SHEET_COLOUR);
764
765     if (!validEnd || !validRes || row == null || row[column] == null
766             || row[column].secondaryStructure != 'E')
767     {
768       g.fillRect(lastSSX, y + 4 + iconOffset,
769               (x * charWidth) - lastSSX - 4, 7);
770       g.fillPolygon(new int[]
771       { (x * charWidth) - 4, (x * charWidth) - 4, (x * charWidth) },
772               new int[]
773               { y + iconOffset, y + 14 + iconOffset, y + 7 + iconOffset },
774               3);
775     }
776     else
777     {
778       g.fillRect(lastSSX, y + 4 + iconOffset,
779               (x + 1) * charWidth - lastSSX, 7);
780     }
781
782   }
783
784   public void drawHelixAnnot(Graphics g, Annotation[] row, int lastSSX,
785           int x, int y, int iconOffset, int startRes, int column,
786           boolean validRes, boolean validEnd)
787   {
788     g.setColor(HELIX_COLOUR);
789
790     int sCol = (lastSSX / charWidth) + startRes;
791     int x1 = lastSSX;
792     int x2 = (x * charWidth);
793
794     if (MAC)
795     {
796       int ofs = charWidth / 2;
797       // Off by 1 offset when drawing rects and ovals
798       // to offscreen image on the MAC
799       g.fillRoundRect(lastSSX, y + 4 + iconOffset, x2 - x1, 8, 8, 8);
800       if (sCol == 0 || row[sCol - 1] == null
801               || row[sCol - 1].secondaryStructure != 'H')
802       {
803       }
804       else
805       {
806         // g.setColor(Color.orange);
807         g.fillRoundRect(lastSSX, y + 4 + iconOffset, x2 - x1 - ofs + 1, 8,
808                 0, 0);
809       }
810       if (!validRes || row[column] == null
811               || row[column].secondaryStructure != 'H')
812       {
813
814       }
815       else
816       {
817         // g.setColor(Color.magenta);
818         g.fillRoundRect(lastSSX + ofs, y + 4 + iconOffset, x2 - x1 - ofs
819                 + 1, 8, 0, 0);
820
821       }
822
823       return;
824     }
825
826     if (sCol == 0 || row[sCol - 1] == null
827             || row[sCol - 1].secondaryStructure != 'H')
828     {
829       g.fillArc(lastSSX, y + 4 + iconOffset, charWidth, 8, 90, 180);
830       x1 += charWidth / 2;
831     }
832
833     if (!validRes || row[column] == null
834             || row[column].secondaryStructure != 'H')
835     {
836       g.fillArc((x * charWidth) - charWidth, y + 4 + iconOffset, charWidth,
837               8, 270, 180);
838       x2 -= charWidth / 2;
839     }
840
841     g.fillRect(x1, y + 4 + iconOffset, x2 - x1, 8);
842   }
843
844   public void drawLineGraph(Graphics g, AlignmentAnnotation _aa,
845           Annotation[] aa_annotations, int sRes, int eRes, int y,
846           float min, float max, int graphHeight)
847   {
848     if (sRes > aa_annotations.length)
849     {
850       return;
851     }
852
853     int x = 0;
854
855     // Adjustment for fastpaint to left
856     if (eRes < endRes)
857     {
858       eRes++;
859     }
860
861     eRes = Math.min(eRes, aa_annotations.length);
862
863     if (sRes == 0)
864     {
865       x++;
866     }
867
868     int y1 = y, y2 = y;
869     float range = max - min;
870
871     // //Draw origin
872     if (min < 0)
873     {
874       y2 = y - (int) ((0 - min / range) * graphHeight);
875     }
876
877     g.setColor(Color.gray);
878     g.drawLine(x - charWidth, y2, (eRes - sRes + 1) * charWidth, y2);
879
880     eRes = Math.min(eRes, aa_annotations.length);
881
882     int column;
883     int aaMax = aa_annotations.length - 1;
884
885     while (x < eRes - sRes)
886     {
887       column = sRes + x;
888       if (hasHiddenColumns)
889       {
890         column = columnSelection.adjustForHiddenColumns(column);
891       }
892
893       if (column > aaMax)
894       {
895         break;
896       }
897
898       if (aa_annotations[column] == null
899               || aa_annotations[column - 1] == null)
900       {
901         x++;
902         continue;
903       }
904
905       if (aa_annotations[column].colour == null)
906         g.setColor(Color.black);
907       else
908         g.setColor(aa_annotations[column].colour);
909
910       y1 = y
911               - (int) (((aa_annotations[column - 1].value - min) / range) * graphHeight);
912       y2 = y
913               - (int) (((aa_annotations[column].value - min) / range) * graphHeight);
914
915       g.drawLine(x * charWidth - charWidth / 2, y1, x * charWidth
916               + charWidth / 2, y2);
917       x++;
918     }
919
920     if (_aa.threshold != null)
921     {
922       g.setColor(_aa.threshold.colour);
923       Graphics2D g2 = (Graphics2D) g;
924       g2.setStroke(new BasicStroke(1, BasicStroke.CAP_SQUARE,
925               BasicStroke.JOIN_ROUND, 3f, new float[]
926               { 5f, 3f }, 0f));
927
928       y2 = (int) (y - ((_aa.threshold.value - min) / range) * graphHeight);
929       g.drawLine(0, y2, (eRes - sRes) * charWidth, y2);
930       g2.setStroke(new BasicStroke());
931     }
932   }
933
934   public void drawBarGraph(Graphics g, AlignmentAnnotation _aa,
935           Annotation[] aa_annotations, int sRes, int eRes, float min,
936           float max, int y, boolean renderHistogram,boolean renderProfile,boolean normaliseProfile)
937   {
938     if (sRes > aa_annotations.length)
939     {
940       return;
941     }
942     Font ofont = g.getFont();
943     eRes = Math.min(eRes, aa_annotations.length);
944
945     int x = 0, y1 = y, y2 = y;
946
947     float range = max - min;
948
949     if (min < 0)
950     {
951       y2 = y - (int) ((0 - min / (range)) * _aa.graphHeight);
952     }
953
954     g.setColor(Color.gray);
955
956     g.drawLine(x, y2, (eRes - sRes) * charWidth, y2);
957
958     int column;
959     int aaMax = aa_annotations.length - 1;
960     while (x < eRes - sRes)
961     {
962       column = sRes + x;
963       if (hasHiddenColumns)
964       {
965         column = columnSelection.adjustForHiddenColumns(column);
966       }
967
968       if (column > aaMax)
969       {
970         break;
971       }
972
973       if (aa_annotations[column] == null)
974       {
975         x++;
976         continue;
977       }
978       if (aa_annotations[column].colour == null)
979         g.setColor(Color.black);
980       else
981         g.setColor(aa_annotations[column].colour);
982
983       y1 = y
984               - (int) (((aa_annotations[column].value - min) / (range)) * _aa.graphHeight);
985
986       if (renderHistogram)
987       {
988         if (y1 - y2 > 0)
989         {
990           g.fillRect(x * charWidth, y2, charWidth, y1 - y2);
991         }
992         else
993         {
994           g.fillRect(x * charWidth, y1, charWidth, y2 - y1);
995         }
996       }
997       // draw profile if available
998       if (renderProfile)
999       {
1000
1001         int profl[] = getProfileFor(_aa, column);
1002         // just try to draw the logo if profl is not null
1003         if (profl != null && profl[1] != 0)
1004         {
1005           float ht = normaliseProfile ? y - _aa.graphHeight : y1;
1006           double htn = normaliseProfile ? _aa.graphHeight : (y2 - y1);// aa.graphHeight;
1007           double hght;
1008           float wdth;
1009           double ht2 = 0;
1010           char[] dc;
1011
1012           /**
1013            * profl.length == 74 indicates that the profile of a secondary
1014            * structure conservation row was accesed. Therefore dc gets length 2,
1015            * to have space for a basepair instead of just a single nucleotide
1016            */
1017           if (profl.length == 74)
1018           {
1019             dc = new char[2];
1020           }
1021           else
1022           {
1023             dc = new char[1];
1024           }
1025           LineMetrics lm = g.getFontMetrics(ofont).getLineMetrics("Q", g);
1026           double scale = 1f / (normaliseProfile ? profl[1] : 100f);
1027           float ofontHeight = 1f / lm.getAscent();// magnify to fill box
1028           double scl = 0.0;
1029           for (int c = 2; c < profl[0];)
1030           {
1031             dc[0] = (char) profl[c++];
1032
1033             if (_aa.label.startsWith("StrucConsensus"))
1034             {
1035               dc[1] = (char) profl[c++];
1036             }
1037
1038             wdth = charWidth;
1039             wdth /= fm.charsWidth(dc, 0, dc.length);
1040
1041             ht += scl;
1042             {
1043               scl = htn * scale * profl[c++];
1044               lm = ofont.getLineMetrics(dc, 0, 1, g.getFontMetrics()
1045                       .getFontRenderContext());
1046               g.setFont(ofont.deriveFont(AffineTransform.getScaleInstance(
1047                       wdth, scl / lm.getAscent())));
1048               lm = g.getFontMetrics().getLineMetrics(dc, 0, 1, g);
1049
1050               // Debug - render boxes around characters
1051               // g.setColor(Color.red);
1052               // g.drawRect(x*av.charWidth, (int)ht, av.charWidth,
1053               // (int)(scl));
1054               // g.setColor(profcolour.findColour(dc[0]).darker());
1055               g.setColor(profcolour.findColour(dc[0], column, null));
1056
1057               hght = (ht + (scl - lm.getDescent() - lm.getBaselineOffsets()[lm
1058                       .getBaselineIndex()]));
1059
1060               g.drawChars(dc, 0, dc.length, x * charWidth, (int) hght);
1061             }
1062           }
1063           g.setFont(ofont);
1064         }
1065       }
1066       x++;
1067     }
1068     if (_aa.threshold != null)
1069     {
1070       g.setColor(_aa.threshold.colour);
1071       Graphics2D g2 = (Graphics2D) g;
1072       g2.setStroke(new BasicStroke(1, BasicStroke.CAP_SQUARE,
1073               BasicStroke.JOIN_ROUND, 3f, new float[]
1074               { 5f, 3f }, 0f));
1075
1076       y2 = (int) (y - ((_aa.threshold.value - min) / range)
1077               * _aa.graphHeight);
1078       g.drawLine(0, y2, (eRes - sRes) * charWidth, y2);
1079       g2.setStroke(new BasicStroke());
1080     }
1081   }
1082
1083   // used by overview window
1084   public void drawGraph(Graphics g, AlignmentAnnotation _aa,
1085           Annotation[] aa_annotations, int width, int y, int sRes, int eRes)
1086   {
1087     eRes = Math.min(eRes, aa_annotations.length);
1088     g.setColor(Color.white);
1089     g.fillRect(0, 0, width, y);
1090     g.setColor(new Color(0, 0, 180));
1091
1092     int x = 0, height;
1093
1094     for (int j = sRes; j < eRes; j++)
1095     {
1096       if (aa_annotations[j] != null)
1097       {
1098         if (aa_annotations[j].colour == null)
1099           g.setColor(Color.black);
1100         else
1101           g.setColor(aa_annotations[j].colour);
1102
1103         height = (int) ((aa_annotations[j].value / _aa.graphMax) * y);
1104         if (height > y)
1105         {
1106           height = y;
1107         }
1108
1109         g.fillRect(x, y - height, charWidth, height);
1110       }
1111       x += charWidth;
1112     }
1113   }
1114 }