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