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