update author list in license for (JAL-826)
[jalview.git] / src / jalview / gui / AnnotationPanel.java
1 /*
2  * Jalview - A Sequence Alignment Editor and Viewer (Version 2.7)
3  * Copyright (C) 2011 J Procter, AM Waterhouse, J Engelhardt, LM Lui, 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.gui;
19
20 import java.awt.*;
21 import java.awt.event.*;
22 import java.awt.font.LineMetrics;
23 import java.awt.geom.AffineTransform;
24 import java.awt.image.*;
25 import java.util.Hashtable;
26
27 import javax.swing.*;
28
29 import com.stevesoft.pat.Regex;
30
31 import jalview.analysis.AAFrequency;
32 import jalview.analysis.StructureFrequency;
33 import jalview.datamodel.*;
34 import jalview.schemes.ColourSchemeI;
35
36 /**
37  * DOCUMENT ME!
38  * 
39  * @author $author$
40  * @version $Revision$
41  */
42 public class AnnotationPanel extends JPanel implements MouseListener,
43         MouseMotionListener, ActionListener, AdjustmentListener
44 {
45   final String HELIX = "Helix";
46
47   final String SHEET = "Sheet";
48
49   /**
50    * For RNA secondary structure "stems" aka helices
51    */
52   final String STEM = "RNA Helix";
53
54   final String LABEL = "Label";
55
56   final String REMOVE = "Remove Annotation";
57
58   final String COLOUR = "Colour";
59
60   final Color HELIX_COLOUR = Color.red.darker();
61
62   final Color SHEET_COLOUR = Color.green.darker().darker();
63
64   final Color STEM_COLOUR = Color.blue.darker();
65
66   /** DOCUMENT ME!! */
67   AlignViewport av;
68
69   AlignmentPanel ap;
70
71   int activeRow = -1;
72
73   BufferedImage image;
74
75   BufferedImage fadedImage;
76
77   Graphics2D gg;
78
79   FontMetrics fm;
80
81   int imgWidth = 0;
82
83   boolean fastPaint = false;
84
85   // Used For mouse Dragging and resizing graphs
86   int graphStretch = -1;
87
88   int graphStretchY = -1;
89
90   int min; // used by mouseDragged to see if user
91
92   int max; // used by mouseDragged to see if user
93
94   boolean mouseDragging = false;
95
96   boolean MAC = false;
97
98   // for editing cursor
99   int cursorX = 0;
100
101   int cursorY = 0;
102
103   /**
104    * Creates a new AnnotationPanel object.
105    * 
106    * @param ap
107    *          DOCUMENT ME!
108    */
109   public AnnotationPanel(AlignmentPanel ap)
110   {
111
112     MAC = new jalview.util.Platform().isAMac();
113
114     ToolTipManager.sharedInstance().registerComponent(this);
115     ToolTipManager.sharedInstance().setInitialDelay(0);
116     ToolTipManager.sharedInstance().setDismissDelay(10000);
117     this.ap = ap;
118     av = ap.av;
119     this.setLayout(null);
120     addMouseListener(this);
121     addMouseMotionListener(this);
122     ap.annotationScroller.getVerticalScrollBar()
123             .addAdjustmentListener(this);
124   }
125
126   public AnnotationPanel(AlignViewport av)
127   {
128     this.av = av;
129   }
130
131   /**
132    * DOCUMENT ME!
133    * 
134    * @param evt
135    *          DOCUMENT ME!
136    */
137   public void adjustmentValueChanged(AdjustmentEvent evt)
138   {
139     ap.alabels.setScrollOffset(-evt.getValue());
140   }
141
142   /**
143    * Calculates the height of the annotation displayed in the annotation panel.
144    * Callers should normally call the ap.adjustAnnotationHeight method to ensure
145    * all annotation associated components are updated correctly.
146    * 
147    */
148   public int adjustPanelHeight()
149   {
150     int height = calcPanelHeight();
151     this.setPreferredSize(new Dimension(1, height));
152     if (ap != null)
153     {
154       // revalidate only when the alignment panel is fully constructed
155       ap.validate();
156     }
157
158     return height;
159   }
160
161   /**
162    * calculate the height for visible annotation, revalidating bounds where
163    * necessary ABSTRACT GUI METHOD
164    * 
165    * @return total height of annotation
166    */
167   public int calcPanelHeight()
168   {
169     // setHeight of panels
170     AlignmentAnnotation[] aa = av.alignment.getAlignmentAnnotation();
171     int height = 0;
172
173     if (aa != null)
174     {
175       for (int i = 0; i < aa.length; i++)
176       {
177         if (aa[i] == null)
178         {
179           System.err.println("Null annotation row: ignoring.");
180           continue;
181         }
182         if (!aa[i].visible)
183         {
184           continue;
185         }
186
187         aa[i].height = 0;
188
189         if (aa[i].hasText)
190         {
191           aa[i].height += av.charHeight;
192         }
193
194         if (aa[i].hasIcons)
195         {
196           aa[i].height += 16;
197         }
198
199         if (aa[i].graph > 0)
200         {
201           aa[i].height += aa[i].graphHeight;
202         }
203
204         if (aa[i].height == 0)
205         {
206           aa[i].height = 20;
207         }
208
209         height += aa[i].height;
210       }
211     }
212     if (height == 0)
213     {
214       // set minimum
215       height = 20;
216     }
217     return height;
218   }
219
220   /**
221    * DOCUMENT ME!
222    * 
223    * @param evt
224    *          DOCUMENT ME!
225    */
226   public void actionPerformed(ActionEvent evt)
227   {
228     AlignmentAnnotation[] aa = av.alignment.getAlignmentAnnotation();
229     if (aa == null)
230     {
231       return;
232     }
233     Annotation[] anot = aa[activeRow].annotations;
234
235     if (anot.length < av.getColumnSelection().getMax())
236     {
237       Annotation[] temp = new Annotation[av.getColumnSelection().getMax() + 2];
238       System.arraycopy(anot, 0, temp, 0, anot.length);
239       anot = temp;
240       aa[activeRow].annotations = anot;
241     }
242
243     if (evt.getActionCommand().equals(REMOVE))
244     {
245       for (int i = 0; i < av.getColumnSelection().size(); i++)
246       {
247         anot[av.getColumnSelection().columnAt(i)] = null;
248       }
249     }
250     else if (evt.getActionCommand().equals(LABEL))
251     {
252       String exMesg = collectAnnotVals(anot, av.getColumnSelection(), LABEL);
253       String label = JOptionPane.showInputDialog(this, "Enter label",
254               exMesg);
255
256       if (label == null)
257       {
258         return;
259       }
260
261       if ((label.length() > 0) && !aa[activeRow].hasText)
262       {
263         aa[activeRow].hasText = true;
264       }
265
266       for (int i = 0; i < av.getColumnSelection().size(); i++)
267       {
268         int index = av.getColumnSelection().columnAt(i);
269
270         if (!av.colSel.isVisible(index))
271           continue;
272
273         if (anot[index] == null)
274         {
275           anot[index] = new Annotation(label, "", ' ', 0); // TODO: verify that
276           // null exceptions
277           // aren't raised
278           // elsewhere.
279         }
280         else
281         {
282           anot[index].displayCharacter = label;
283         }
284       }
285     }
286     else if (evt.getActionCommand().equals(COLOUR))
287     {
288       Color col = JColorChooser.showDialog(this,
289               "Choose foreground colour", Color.black);
290
291       for (int i = 0; i < av.getColumnSelection().size(); i++)
292       {
293         int index = av.getColumnSelection().columnAt(i);
294
295         if (!av.colSel.isVisible(index))
296           continue;
297
298         if (anot[index] == null)
299         {
300           anot[index] = new Annotation("", "", ' ', 0);
301         }
302
303         anot[index].colour = col;
304       }
305     }
306     else
307     // HELIX OR SHEET
308     {
309       char type = 0;
310       String symbol = "\u03B1";
311
312       if (evt.getActionCommand().equals(HELIX))
313       {
314         type = 'H';
315       }
316       else if (evt.getActionCommand().equals(SHEET))
317       {
318         type = 'E';
319         symbol = "\u03B2";
320       }
321
322       // Added by LML to color stems
323       else if (evt.getActionCommand().equals(STEM))
324       {
325         type = 'S';
326         symbol = "\u03C3";
327       }
328
329       if (!aa[activeRow].hasIcons)
330       {
331         aa[activeRow].hasIcons = true;
332       }
333
334       String label = JOptionPane.showInputDialog(
335               "Enter a label for the structure?", symbol);
336
337       if (label == null)
338       {
339         return;
340       }
341
342       if ((label.length() > 0) && !aa[activeRow].hasText)
343       {
344         aa[activeRow].hasText = true;
345       }
346
347       for (int i = 0; i < av.getColumnSelection().size(); i++)
348       {
349         int index = av.getColumnSelection().columnAt(i);
350
351         if (!av.colSel.isVisible(index))
352           continue;
353
354         if (anot[index] == null)
355         {
356           anot[index] = new Annotation(label, "", type, 0);
357         }
358
359         anot[index].secondaryStructure = type;
360         anot[index].displayCharacter = label;
361       }
362     }
363
364     adjustPanelHeight();
365     repaint();
366
367     return;
368   }
369
370   private String collectAnnotVals(Annotation[] anot,
371           ColumnSelection columnSelection, String label2)
372   {
373     String collatedInput = "";
374     String last = "";
375     for (int i = 0; i < columnSelection.size(); i++)
376     {
377       int index = columnSelection.columnAt(i);
378       // always check for current display state - just in case
379       if (!av.colSel.isVisible(index))
380         continue;
381       String tlabel = null;
382       if (anot[index] != null)
383       { // LML added stem code
384         if (label2.equals(HELIX) || label2.equals(SHEET)
385                 || label2.equals(STEM) || label2.equals(LABEL))
386         {
387           tlabel = anot[index].description;
388           if (tlabel == null || tlabel.length() < 1)
389           {
390             if (label2.equals(HELIX) || label2.equals(SHEET)
391                     || label2.equals(STEM))
392             {
393               tlabel = "" + anot[index].secondaryStructure;
394             }
395             else
396             {
397               tlabel = "" + anot[index].displayCharacter;
398             }
399           }
400         }
401         if (tlabel != null && !tlabel.equals(last))
402         {
403           if (last.length() > 0)
404           {
405             collatedInput += " ";
406           }
407           collatedInput += tlabel;
408         }
409       }
410     }
411     return collatedInput;
412   }
413
414   /**
415    * DOCUMENT ME!
416    * 
417    * @param evt
418    *          DOCUMENT ME!
419    */
420   public void mousePressed(MouseEvent evt)
421   {
422
423     AlignmentAnnotation[] aa = av.alignment.getAlignmentAnnotation();
424     if (aa == null)
425     {
426       return;
427     }
428
429     int height = 0;
430     activeRow = -1;
431
432     for (int i = 0; i < aa.length; i++)
433     {
434       if (aa[i].visible)
435       {
436         height += aa[i].height;
437       }
438
439       if (evt.getY() < height)
440       {
441         if (aa[i].editable)
442         {
443           activeRow = i;
444         }
445         else if (aa[i].graph > 0)
446         {
447           // Stretch Graph
448           graphStretch = i;
449           graphStretchY = evt.getY();
450         }
451
452         break;
453       }
454     }
455
456     if (SwingUtilities.isRightMouseButton(evt) && activeRow != -1)
457     {
458       if (av.getColumnSelection() == null)
459       {
460         return;
461       }
462
463       JPopupMenu pop = new JPopupMenu("Structure type");
464       JMenuItem item;
465       /*
466        * Just display the needed structure options
467        */
468       if (av.alignment.isNucleotide() == true)
469       {
470         item = new JMenuItem(STEM);
471         item.addActionListener(this);
472         pop.add(item);
473       }
474       else
475       {
476         item = new JMenuItem(HELIX);
477         item.addActionListener(this);
478         pop.add(item);
479         item = new JMenuItem(SHEET);
480         item.addActionListener(this);
481         pop.add(item);
482       }
483       item = new JMenuItem(LABEL);
484       item.addActionListener(this);
485       pop.add(item);
486       item = new JMenuItem(COLOUR);
487       item.addActionListener(this);
488       pop.add(item);
489       item = new JMenuItem(REMOVE);
490       item.addActionListener(this);
491       pop.add(item);
492       pop.show(this, evt.getX(), evt.getY());
493
494       return;
495     }
496
497     if (aa == null)
498     {
499       return;
500     }
501
502     ap.scalePanel.mousePressed(evt);
503
504   }
505
506   /**
507    * DOCUMENT ME!
508    * 
509    * @param evt
510    *          DOCUMENT ME!
511    */
512   public void mouseReleased(MouseEvent evt)
513   {
514     graphStretch = -1;
515     graphStretchY = -1;
516     mouseDragging = false;
517     ap.scalePanel.mouseReleased(evt);
518   }
519
520   /**
521    * DOCUMENT ME!
522    * 
523    * @param evt
524    *          DOCUMENT ME!
525    */
526   public void mouseEntered(MouseEvent evt)
527   {
528     ap.scalePanel.mouseEntered(evt);
529   }
530
531   /**
532    * DOCUMENT ME!
533    * 
534    * @param evt
535    *          DOCUMENT ME!
536    */
537   public void mouseExited(MouseEvent evt)
538   {
539     ap.scalePanel.mouseExited(evt);
540   }
541
542   /**
543    * DOCUMENT ME!
544    * 
545    * @param evt
546    *          DOCUMENT ME!
547    */
548   public void mouseDragged(MouseEvent evt)
549   {
550     if (graphStretch > -1)
551     {
552       av.alignment.getAlignmentAnnotation()[graphStretch].graphHeight += graphStretchY
553               - evt.getY();
554       if (av.alignment.getAlignmentAnnotation()[graphStretch].graphHeight < 0)
555       {
556         av.alignment.getAlignmentAnnotation()[graphStretch].graphHeight = 0;
557       }
558       graphStretchY = evt.getY();
559       adjustPanelHeight();
560       ap.paintAlignment(true);
561     }
562     else
563     {
564       ap.scalePanel.mouseDragged(evt);
565     }
566   }
567
568   /**
569    * DOCUMENT ME!
570    * 
571    * @param evt
572    *          DOCUMENT ME!
573    */
574   public void mouseMoved(MouseEvent evt)
575   {
576     AlignmentAnnotation[] aa = av.alignment.getAlignmentAnnotation();
577
578     if (aa == null)
579     {
580       this.setToolTipText(null);
581       return;
582     }
583
584     int row = -1;
585     int height = 0;
586
587     for (int i = 0; i < aa.length; i++)
588     {
589       if (aa[i].visible)
590       {
591         height += aa[i].height;
592       }
593
594       if (evt.getY() < height)
595       {
596         row = i;
597
598         break;
599       }
600     }
601
602     if (row == -1)
603     {
604       this.setToolTipText(null);
605       return;
606     }
607
608     int res = (evt.getX() / av.getCharWidth()) + av.getStartRes();
609
610     if (av.hasHiddenColumns)
611     {
612       res = av.getColumnSelection().adjustForHiddenColumns(res);
613     }
614
615     if (row > -1 && aa[row].annotations != null
616             && res < (int) aa[row].annotations.length)
617     {
618       if (aa[row].graphGroup > -1)
619       {
620         StringBuffer tip = new StringBuffer("<html>");
621         for (int gg = 0; gg < aa.length; gg++)
622         {
623           if (aa[gg].graphGroup == aa[row].graphGroup
624                   && aa[gg].annotations[res] != null)
625           {
626             tip.append(aa[gg].label + " "
627                     + aa[gg].annotations[res].description + "<br>");
628           }
629         }
630         if (tip.length() != 6)
631         {
632           tip.setLength(tip.length() - 4);
633           this.setToolTipText(tip.toString() + "</html>");
634         }
635       }
636       else if (aa[row].annotations[res] != null
637               && aa[row].annotations[res].description != null
638               && aa[row].annotations[res].description.length() > 0)
639       {
640         this.setToolTipText(aa[row].annotations[res].description);
641       }
642       else
643       {
644         // clear the tooltip.
645         this.setToolTipText(null);
646       }
647
648       if (aa[row].annotations[res] != null)
649       {
650         StringBuffer text = new StringBuffer("Sequence position "
651                 + (res + 1));
652
653         if (aa[row].annotations[res].description != null)
654         {
655           text.append("  " + aa[row].annotations[res].description);
656         }
657
658         ap.alignFrame.statusBar.setText(text.toString());
659       }
660     }
661     else
662     {
663       this.setToolTipText(null);
664     }
665   }
666
667   /**
668    * DOCUMENT ME!
669    * 
670    * @param evt
671    *          DOCUMENT ME!
672    */
673   public void mouseClicked(MouseEvent evt)
674   {
675     if (activeRow != -1)
676     {
677       AlignmentAnnotation[] aa = av.alignment.getAlignmentAnnotation();
678       AlignmentAnnotation anot = aa[activeRow];
679
680       if (anot.description.equals("secondary structure"))
681       {
682         // System.out.println(anot.description+" "+anot.getRNAStruc());
683       }
684     }
685   }
686
687   // TODO mouseClicked-content and drawCursor are quite experimental!
688   public void drawCursor(Graphics graphics, SequenceI seq, int res, int x1,
689           int y1)
690   {
691     int pady = av.charHeight / 5;
692     int charOffset = 0;
693     graphics.setColor(Color.black);
694     graphics.fillRect(x1, y1, av.charWidth, av.charHeight);
695
696     if (av.validCharWidth)
697     {
698       graphics.setColor(Color.white);
699
700       char s = seq.getCharAt(res);
701
702       charOffset = (av.charWidth - fm.charWidth(s)) / 2;
703       graphics.drawString(String.valueOf(s), charOffset + x1,
704               (y1 + av.charHeight) - pady);
705     }
706
707   }
708
709   /**
710    * DOCUMENT ME!
711    * 
712    * @param g
713    *          DOCUMENT ME!
714    */
715   public void paintComponent(Graphics g)
716   {
717     g.setColor(Color.white);
718     g.fillRect(0, 0, getWidth(), getHeight());
719
720     if (image != null)
721     {
722       if (fastPaint || (getVisibleRect().width != g.getClipBounds().width)
723               || (getVisibleRect().height != g.getClipBounds().height))
724       {
725         g.drawImage(image, 0, 0, this);
726         fastPaint = false;
727         return;
728       }
729     }
730     imgWidth = (av.endRes - av.startRes + 1) * av.charWidth;
731     if (imgWidth < 1)
732       return;
733     if (image == null || imgWidth != image.getWidth()
734             || image.getHeight(this) != getHeight())
735     {
736       image = new BufferedImage(imgWidth, ap.annotationPanel.getHeight(),
737               BufferedImage.TYPE_INT_RGB);
738       gg = (Graphics2D) image.getGraphics();
739
740       if (av.antiAlias)
741       {
742         gg.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
743                 RenderingHints.VALUE_ANTIALIAS_ON);
744       }
745
746       gg.setFont(av.getFont());
747       fm = gg.getFontMetrics();
748       gg.setColor(Color.white);
749       gg.fillRect(0, 0, imgWidth, image.getHeight());
750     }
751
752     drawComponent(gg, av.startRes, av.endRes + 1);
753     g.drawImage(image, 0, 0, this);
754   }
755
756   /**
757    * non-Thread safe repaint
758    * 
759    * @param horizontal
760    *          repaint with horizontal shift in alignment
761    */
762   public void fastPaint(int horizontal)
763   {
764
765     if ((horizontal == 0) || gg == null
766             || av.alignment.getAlignmentAnnotation() == null
767             || av.alignment.getAlignmentAnnotation().length < 1
768             || av.updatingConsensus || av.updatingConservation)
769     {
770       repaint();
771       return;
772     }
773     gg.copyArea(0, 0, imgWidth, getHeight(), -horizontal * av.charWidth, 0);
774
775     int sr = av.startRes;
776     int er = av.endRes + 1;
777     int transX = 0;
778
779     if (horizontal > 0) // scrollbar pulled right, image to the left
780     {
781       transX = (er - sr - horizontal) * av.charWidth;
782       sr = er - horizontal;
783     }
784     else if (horizontal < 0)
785     {
786       er = sr - horizontal;
787     }
788
789     gg.translate(transX, 0);
790
791     drawComponent(gg, sr, er);
792
793     gg.translate(-transX, 0);
794
795     fastPaint = true;
796     repaint();
797
798   }
799
800   /**
801    * DOCUMENT ME!
802    * 
803    * @param g
804    *          DOCUMENT ME!
805    * @param startRes
806    *          DOCUMENT ME!
807    * @param endRes
808    *          DOCUMENT ME!
809    */
810   public void drawComponent(Graphics g, int startRes, int endRes)
811   {
812     if (av.updatingConsensus || av.updatingConservation)
813     {
814       if (image == null)
815       {
816         return;
817       }
818       // We'll keep a record of the old image,
819       // and draw a faded image until the calculation
820       // has completed
821       if (fadedImage == null || fadedImage.getWidth() != imgWidth
822               || fadedImage.getHeight() != image.getHeight())
823       {
824         fadedImage = new BufferedImage(imgWidth, image.getHeight(),
825                 BufferedImage.TYPE_INT_RGB);
826
827         Graphics2D fadedG = (Graphics2D) fadedImage.getGraphics();
828
829         fadedG.setColor(Color.white);
830         fadedG.fillRect(0, 0, imgWidth, image.getHeight());
831
832         fadedG.setComposite(AlphaComposite.getInstance(
833                 AlphaComposite.SRC_OVER, .3f));
834         fadedG.drawImage(image, 0, 0, this);
835
836       }
837
838     }
839     else
840     {
841       fadedImage = null;
842     }
843
844     g.setColor(Color.white);
845     g.fillRect(0, 0, (endRes - startRes) * av.charWidth, getHeight());
846
847     g.setFont(av.getFont());
848     if (fm == null)
849     {
850       fm = g.getFontMetrics();
851     }
852
853     if ((av.alignment.getAlignmentAnnotation() == null)
854             || (av.alignment.getAlignmentAnnotation().length < 1))
855     {
856       g.setColor(Color.white);
857       g.fillRect(0, 0, getWidth(), getHeight());
858       g.setColor(Color.black);
859       if (av.validCharWidth)
860       {
861         g.drawString("Alignment has no annotations", 20, 15);
862       }
863
864       return;
865     }
866
867     AlignmentAnnotation[] aa = av.alignment.getAlignmentAnnotation();
868
869     int x = 0, y = 0;
870     int column = 0;
871     char lastSS;
872     int lastSSX;
873     int iconOffset = 0;
874     boolean validRes = false;
875     boolean validEnd = false;
876     boolean labelAllCols = false;
877     boolean centreColLabels, centreColLabelsDef = av
878             .getCentreColumnLabels();
879     boolean scaleColLabel = false;
880     boolean[] graphGroupDrawn = new boolean[aa.length];
881     int charOffset = 0; // offset for a label
882     float fmWidth, fmScaling = 1f; // scaling for a label to fit it into a
883     // column.
884     Font ofont = g.getFont();
885     // \u03B2 \u03B1
886     for (int i = 0; i < aa.length; i++)
887     {
888       AlignmentAnnotation row = aa[i];
889
890       if (!row.visible)
891       {
892         continue;
893       }
894       centreColLabels = row.centreColLabels || centreColLabelsDef;
895       labelAllCols = row.showAllColLabels;
896       scaleColLabel = row.scaleColLabel;
897       lastSS = ' ';
898       lastSSX = 0;
899       if (row.graph > 0)
900       {
901         if (row.graphGroup > -1 && graphGroupDrawn[row.graphGroup])
902         {
903           continue;
904         }
905
906         // this is so that we draw the characters below the graph
907         y += row.height;
908
909         if (row.hasText)
910         {
911           iconOffset = av.charHeight - fm.getDescent();
912           y -= av.charHeight;
913         }
914       }
915       else if (row.hasText)
916       {
917         iconOffset = av.charHeight - fm.getDescent();
918
919       }
920       else
921       {
922         iconOffset = 0;
923       }
924
925       if (av.updatingConsensus && aa[i] == av.consensus)
926       {
927         y += av.charHeight;
928
929         g.drawImage(fadedImage, 0, y - row.height, imgWidth, y, 0, y
930                 - row.height, imgWidth, y, this);
931         g.setColor(Color.black);
932         // g.drawString("Calculating Consensus....",20, y-row.height/2);
933
934         continue;
935       }
936       else if (av.updatingConservation
937               && aa[i].label.equals("Conservation"))
938       {
939
940         y += av.charHeight;
941         g.drawImage(fadedImage, 0, y - row.height, imgWidth, y, 0, y
942                 - row.height, imgWidth, y, this);
943
944         g.setColor(Color.black);
945         // g.drawString("Calculating Conservation.....",20, y-row.height/2);
946
947         continue;
948       }
949       else if (av.updatingConservation && aa[i].label.equals("Quality"))
950       {
951
952         y += av.charHeight;
953         g.drawImage(fadedImage, 0, y - row.height, imgWidth, y, 0, y
954                 - row.height, imgWidth, y, this);
955         g.setColor(Color.black);
956         // / g.drawString("Calculating Quality....",20, y-row.height/2);
957
958         continue;
959       }
960
961       // first pass sets up state for drawing continuation from left-hand column
962       // of startRes
963       x = (startRes == 0) ? 0 : -1;
964       while (x < endRes - startRes)
965       {
966         if (av.hasHiddenColumns)
967         {
968           column = av.getColumnSelection().adjustForHiddenColumns(
969                   startRes + x);
970           if (column > row.annotations.length - 1)
971           {
972             break;
973           }
974         }
975         else
976         {
977           column = startRes + x;
978         }
979
980         if ((row.annotations == null) || (row.annotations.length <= column)
981                 || (row.annotations[column] == null))
982         {
983           validRes = false;
984         }
985         else
986         {
987           validRes = true;
988         }
989         if (x > -1)
990         {
991           if (activeRow == i)
992           {
993             g.setColor(Color.red);
994
995             if (av.getColumnSelection() != null)
996             {
997               for (int n = 0; n < av.getColumnSelection().size(); n++)
998               {
999                 int v = av.getColumnSelection().columnAt(n);
1000
1001                 if (v == column)
1002                 {
1003                   g.fillRect(x * av.charWidth, y, av.charWidth,
1004                           av.charHeight);
1005                 }
1006               }
1007             }
1008           }
1009
1010           if (av.validCharWidth
1011                   && validRes
1012                   && row.annotations[column].displayCharacter != null
1013                   && (row.annotations[column].displayCharacter.length() > 0))
1014           {
1015
1016             if (centreColLabels || scaleColLabel)
1017             {
1018               fmWidth = (float) fm.charsWidth(
1019                       row.annotations[column].displayCharacter
1020                               .toCharArray(), 0,
1021                       row.annotations[column].displayCharacter.length());
1022
1023               if (scaleColLabel)
1024               {
1025                 // justify the label and scale to fit in column
1026                 if (fmWidth > av.charWidth)
1027                 {
1028                   // scale only if the current font isn't already small enough
1029                   fmScaling = av.charWidth;
1030                   fmScaling /= fmWidth;
1031                   g.setFont(ofont.deriveFont(AffineTransform
1032                           .getScaleInstance(fmScaling, 1.0)));
1033                   // and update the label's width to reflect the scaling.
1034                   fmWidth = av.charWidth;
1035                 }
1036               }
1037             }
1038             else
1039             {
1040               fmWidth = (float) fm
1041                       .charWidth(row.annotations[column].displayCharacter
1042                               .charAt(0));
1043             }
1044             charOffset = (int) ((av.charWidth - fmWidth) / 2f);
1045
1046             if (row.annotations[column].colour == null)
1047               g.setColor(Color.black);
1048             else
1049               g.setColor(row.annotations[column].colour);
1050
1051             if (column == 0 || row.graph > 0)
1052             {
1053               g.drawString(row.annotations[column].displayCharacter,
1054                       (x * av.charWidth) + charOffset, y + iconOffset);
1055             }
1056             else if (row.annotations[column - 1] == null
1057                     || (labelAllCols
1058                             || !row.annotations[column].displayCharacter
1059                                     .equals(row.annotations[column - 1].displayCharacter) || (row.annotations[column].displayCharacter
1060                             .length() < 2 && row.annotations[column].secondaryStructure == ' ')))
1061             {
1062               g.drawString(row.annotations[column].displayCharacter, x
1063                       * av.charWidth + charOffset, y + iconOffset);
1064             }
1065             g.setFont(ofont);
1066           }
1067         }
1068         if (row.hasIcons)
1069         {
1070           char ss = validRes ? row.annotations[column].secondaryStructure
1071                   : ' ';
1072           if (ss == 'S')
1073           {
1074             // distinguish between forward/backward base-pairing
1075             if (row.annotations[column].displayCharacter.indexOf(')') > -1)
1076             {
1077               ss = 's';
1078             }
1079           }
1080           if (!validRes || (ss != lastSS))
1081           {
1082             if (x > -1)
1083             {
1084               switch (lastSS)
1085               {
1086               case 'H':
1087                 drawHelixAnnot(g, row, lastSSX, x, y, iconOffset, startRes,
1088                         column, validRes, validEnd);
1089                 break;
1090
1091               case 'E':
1092                 drawSheetAnnot(g, row, lastSSX, x, y, iconOffset, startRes,
1093                         column, validRes, validEnd);
1094                 break;
1095
1096               case 'S': // Stem case for RNA secondary structure
1097               case 's': // and opposite direction
1098                 drawStemAnnot(g, row, lastSSX, x, y, iconOffset, startRes,
1099                         column, validRes, validEnd);
1100                 break;
1101
1102               default:
1103                 g.setColor(Color.gray);
1104                 g.fillRect(lastSSX, y + 6 + iconOffset, (x * av.charWidth)
1105                         - lastSSX, 2);
1106
1107                 break;
1108               }
1109             }
1110             if (validRes)
1111             {
1112               lastSS = ss;
1113             }
1114             else
1115             {
1116               lastSS = ' ';
1117             }
1118             if (x > -1)
1119             {
1120               lastSSX = (x * av.charWidth);
1121             }
1122           }
1123         }
1124         column++;
1125         x++;
1126       }
1127       if (column >= row.annotations.length)
1128       {
1129         column = row.annotations.length - 1;
1130         validEnd = false;
1131       }
1132       else
1133       {
1134         validEnd = true;
1135       }
1136
1137       // x ++;
1138
1139       if (row.hasIcons)
1140       {
1141         switch (lastSS)
1142         {
1143         case 'H':
1144           drawHelixAnnot(g, row, lastSSX, x, y, iconOffset, startRes,
1145                   column, validRes, validEnd);
1146           break;
1147
1148         case 'E':
1149           drawSheetAnnot(g, row, lastSSX, x, y, iconOffset, startRes,
1150                   column, validRes, validEnd);
1151           break;
1152         case 's':
1153         case 'S': // Stem case for RNA secondary structure
1154           drawStemAnnot(g, row, lastSSX, x, y, iconOffset, startRes,
1155                   column, validRes, validEnd);
1156           break;
1157         default:
1158           drawGlyphLine(g, row, lastSSX, x, y, iconOffset, startRes,
1159                   column, validRes, validEnd);
1160           break;
1161         }
1162       }
1163
1164       if (row.graph > 0 && row.graphHeight > 0)
1165       {
1166         if (row.graph == AlignmentAnnotation.LINE_GRAPH)
1167         {
1168           if (row.graphGroup > -1 && !graphGroupDrawn[row.graphGroup])
1169           {
1170             float groupmax = -999999, groupmin = 9999999;
1171             for (int gg = 0; gg < aa.length; gg++)
1172             {
1173               if (aa[gg].graphGroup != row.graphGroup)
1174               {
1175                 continue;
1176               }
1177
1178               if (aa[gg] != row)
1179               {
1180                 aa[gg].visible = false;
1181               }
1182
1183               if (aa[gg].graphMax > groupmax)
1184               {
1185                 groupmax = aa[gg].graphMax;
1186               }
1187               if (aa[gg].graphMin < groupmin)
1188               {
1189                 groupmin = aa[gg].graphMin;
1190               }
1191             }
1192
1193             for (int gg = 0; gg < aa.length; gg++)
1194             {
1195               if (aa[gg].graphGroup == row.graphGroup)
1196               {
1197                 drawLineGraph(g, aa[gg], startRes, endRes, y, groupmin,
1198                         groupmax, row.graphHeight);
1199               }
1200             }
1201
1202             graphGroupDrawn[row.graphGroup] = true;
1203           }
1204           else
1205           {
1206             drawLineGraph(g, row, startRes, endRes, y, row.graphMin,
1207                     row.graphMax, row.graphHeight);
1208           }
1209         }
1210         else if (row.graph == AlignmentAnnotation.BAR_GRAPH)
1211         {
1212           drawBarGraph(g, row, startRes, endRes, row.graphMin,
1213                   row.graphMax, y);
1214         }
1215       }
1216
1217       if (row.graph > 0 && row.hasText)
1218       {
1219         y += av.charHeight;
1220       }
1221
1222       if (row.graph == 0)
1223       {
1224         y += aa[i].height;
1225       }
1226     }
1227   }
1228
1229   private void drawStemAnnot(Graphics g, AlignmentAnnotation row,
1230           int lastSSX, int x, int y, int iconOffset, int startRes,
1231           int column, boolean validRes, boolean validEnd)
1232   {
1233     g.setColor(STEM_COLOUR);
1234     int sCol = (lastSSX / av.charWidth) + startRes;
1235     int x1 = lastSSX;
1236     int x2 = (x * av.charWidth);
1237     Regex closeparen = new Regex("(\\))");
1238
1239     String dc = column == 0 ? ""
1240             : row.annotations[column - 1].displayCharacter;
1241
1242     boolean diffupstream = sCol == 0 || row.annotations[sCol - 1] == null
1243             || !dc.equals(row.annotations[sCol - 1].displayCharacter);
1244     boolean diffdownstream = !validRes || !validEnd
1245             || row.annotations[column] == null
1246             || !dc.equals(row.annotations[column].displayCharacter);
1247     // System.out.println("Column "+column+" diff up: "+diffupstream+" down:"+diffdownstream);
1248     // If a closing base pair half of the stem, display a backward arrow
1249     if (column > 0 && closeparen.search(dc))
1250     {
1251       if (diffupstream)
1252       // if (validRes && column>1 && row.annotations[column-2]!=null &&
1253       // dc.equals(row.annotations[column-2].displayCharacter))
1254       {
1255         g.fillPolygon(new int[]
1256         { lastSSX + 5, lastSSX + 5, lastSSX }, new int[]
1257         { y + iconOffset, y + 14 + iconOffset, y + 8 + iconOffset }, 3);
1258         x1 += 5;
1259       }
1260       if (diffdownstream)
1261       {
1262         x2 -= 1;
1263       }
1264     }
1265     else
1266     {
1267       // display a forward arrow
1268       if (diffdownstream)
1269       {
1270         g.fillPolygon(new int[]
1271         { x2 - 5, x2 - 5, x2 }, new int[]
1272         { y + iconOffset, y + 14 + iconOffset, y + 8 + iconOffset }, 3);
1273         x2 -= 5;
1274       }
1275       if (diffupstream)
1276       {
1277         x1 += 1;
1278       }
1279     }
1280     // draw arrow body
1281     g.fillRect(x1, y + 4 + iconOffset, x2 - x1, 7);
1282   }
1283
1284   private void drawGlyphLine(Graphics g, AlignmentAnnotation row,
1285           int lastSSX, int x, int y, int iconOffset, int startRes,
1286           int column, boolean validRes, boolean validEnd)
1287   {
1288     g.setColor(Color.gray);
1289     g
1290             .fillRect(lastSSX, y + 6 + iconOffset, (x * av.charWidth)
1291                     - lastSSX, 2);
1292   }
1293
1294   private void drawSheetAnnot(Graphics g, AlignmentAnnotation row,
1295           int lastSSX, int x, int y, int iconOffset, int startRes,
1296           int column, boolean validRes, boolean validEnd)
1297   {
1298     g.setColor(SHEET_COLOUR);
1299
1300     if (!validEnd || !validRes || row.annotations[column] == null
1301             || row.annotations[column].secondaryStructure != 'E')
1302     {
1303       g.fillRect(lastSSX, y + 4 + iconOffset, (x * av.charWidth) - lastSSX
1304               - 4, 7);
1305       g.fillPolygon(
1306               new int[]
1307               { (x * av.charWidth) - 4, (x * av.charWidth) - 4,
1308                   (x * av.charWidth) }, new int[]
1309               { y + iconOffset, y + 14 + iconOffset, y + 7 + iconOffset },
1310               3);
1311     }
1312     else
1313     {
1314       g.fillRect(lastSSX, y + 4 + iconOffset, (x + 1) * av.charWidth
1315               - lastSSX, 7);
1316     }
1317
1318   }
1319
1320   private void drawHelixAnnot(Graphics g, AlignmentAnnotation row,
1321           int lastSSX, int x, int y, int iconOffset, int startRes,
1322           int column, boolean validRes, boolean validEnd)
1323   {
1324     g.setColor(HELIX_COLOUR);
1325
1326     int sCol = (lastSSX / av.charWidth) + startRes;
1327     int x1 = lastSSX;
1328     int x2 = (x * av.charWidth);
1329
1330     if (MAC)
1331     {
1332       int ofs = av.charWidth / 2;
1333       // Off by 1 offset when drawing rects and ovals
1334       // to offscreen image on the MAC
1335       g.fillRoundRect(lastSSX, y + 4 + iconOffset, x2 - x1, 8, 8, 8);
1336       if (sCol == 0 || row.annotations[sCol - 1] == null
1337               || row.annotations[sCol - 1].secondaryStructure != 'H')
1338       {
1339       }
1340       else
1341       {
1342         // g.setColor(Color.orange);
1343         g.fillRoundRect(lastSSX, y + 4 + iconOffset, x2 - x1 - ofs + 1, 8,
1344                 0, 0);
1345       }
1346       if (!validRes || row.annotations[column] == null
1347               || row.annotations[column].secondaryStructure != 'H')
1348       {
1349
1350       }
1351       else
1352       {
1353         // g.setColor(Color.magenta);
1354         g.fillRoundRect(lastSSX + ofs, y + 4 + iconOffset, x2 - x1 - ofs
1355                 + 1, 8, 0, 0);
1356
1357       }
1358
1359       return;
1360     }
1361
1362     if (sCol == 0 || row.annotations[sCol - 1] == null
1363             || row.annotations[sCol - 1].secondaryStructure != 'H')
1364     {
1365       g.fillArc(lastSSX, y + 4 + iconOffset, av.charWidth, 8, 90, 180);
1366       x1 += av.charWidth / 2;
1367     }
1368
1369     if (!validRes || row.annotations[column] == null
1370             || row.annotations[column].secondaryStructure != 'H')
1371     {
1372       g.fillArc((x * av.charWidth) - av.charWidth, y + 4 + iconOffset,
1373               av.charWidth, 8, 270, 180);
1374       x2 -= av.charWidth / 2;
1375     }
1376
1377     g.fillRect(x1, y + 4 + iconOffset, x2 - x1, 8);
1378   }
1379
1380   public void drawLineGraph(Graphics g, AlignmentAnnotation aa, int sRes,
1381           int eRes, int y, float min, float max, int graphHeight)
1382   {
1383     if (sRes > aa.annotations.length)
1384     {
1385       return;
1386     }
1387
1388     int x = 0;
1389
1390     // Adjustment for fastpaint to left
1391     if (eRes < av.endRes)
1392     {
1393       eRes++;
1394     }
1395
1396     eRes = Math.min(eRes, aa.annotations.length);
1397
1398     if (sRes == 0)
1399     {
1400       x++;
1401     }
1402
1403     int y1 = y, y2 = y;
1404     float range = max - min;
1405
1406     // //Draw origin
1407     if (min < 0)
1408     {
1409       y2 = y - (int) ((0 - min / range) * graphHeight);
1410     }
1411
1412     g.setColor(Color.gray);
1413     g.drawLine(x - av.charWidth, y2, (eRes - sRes + 1) * av.charWidth, y2);
1414
1415     eRes = Math.min(eRes, aa.annotations.length);
1416
1417     int column;
1418     int aaMax = aa.annotations.length - 1;
1419
1420     while (x < eRes - sRes)
1421     {
1422       column = sRes + x;
1423       if (av.hasHiddenColumns)
1424       {
1425         column = av.getColumnSelection().adjustForHiddenColumns(column);
1426       }
1427
1428       if (column > aaMax)
1429       {
1430         break;
1431       }
1432
1433       if (aa.annotations[column] == null
1434               || aa.annotations[column - 1] == null)
1435       {
1436         x++;
1437         continue;
1438       }
1439
1440       if (aa.annotations[column].colour == null)
1441         g.setColor(Color.black);
1442       else
1443         g.setColor(aa.annotations[column].colour);
1444
1445       y1 = y
1446               - (int) (((aa.annotations[column - 1].value - min) / range) * graphHeight);
1447       y2 = y
1448               - (int) (((aa.annotations[column].value - min) / range) * graphHeight);
1449
1450       g.drawLine(x * av.charWidth - av.charWidth / 2, y1, x * av.charWidth
1451               + av.charWidth / 2, y2);
1452       x++;
1453     }
1454
1455     if (aa.threshold != null)
1456     {
1457       g.setColor(aa.threshold.colour);
1458       Graphics2D g2 = (Graphics2D) g;
1459       g2.setStroke(new BasicStroke(1, BasicStroke.CAP_SQUARE,
1460               BasicStroke.JOIN_ROUND, 3f, new float[]
1461               { 5f, 3f }, 0f));
1462
1463       y2 = (int) (y - ((aa.threshold.value - min) / range) * graphHeight);
1464       g.drawLine(0, y2, (eRes - sRes) * av.charWidth, y2);
1465       g2.setStroke(new BasicStroke());
1466     }
1467   }
1468
1469   public void drawBarGraph(Graphics g, AlignmentAnnotation aa, int sRes,
1470           int eRes, float min, float max, int y)
1471   {
1472     ColourSchemeI profcolour = av.getGlobalColourScheme();
1473     if (profcolour == null)
1474     {
1475       profcolour = new jalview.schemes.ZappoColourScheme();
1476     }
1477     if (sRes > aa.annotations.length)
1478     {
1479       return;
1480     }
1481     Font ofont = g.getFont();
1482     eRes = Math.min(eRes, aa.annotations.length);
1483
1484     int x = 0, y1 = y, y2 = y;
1485
1486     float range = max - min;
1487
1488     if (min < 0)
1489     {
1490       y2 = y - (int) ((0 - min / (range)) * aa.graphHeight);
1491     }
1492
1493     g.setColor(Color.gray);
1494
1495     g.drawLine(x, y2, (eRes - sRes) * av.charWidth, y2);
1496
1497     int column;
1498     int aaMax = aa.annotations.length - 1;
1499     boolean renderHistogram = true, renderProfile = true;
1500     if (aa.autoCalculated && aa.label.startsWith("Consensus"))
1501     {
1502       // TODO: generalise this to have render styles for consensus/profile data
1503       if (aa.groupRef != null)
1504       {
1505         renderHistogram = aa.groupRef.isShowConsensusHistogram();
1506         renderProfile = aa.groupRef.isShowSequenceLogo();
1507       }
1508       else
1509       {
1510         renderHistogram = av.isShowConsensusHistogram();
1511         renderProfile = av.isShowSequenceLogo();
1512       }
1513     }
1514     while (x < eRes - sRes)
1515     {
1516       column = sRes + x;
1517       if (av.hasHiddenColumns)
1518       {
1519         column = av.getColumnSelection().adjustForHiddenColumns(column);
1520       }
1521
1522       if (column > aaMax)
1523       {
1524         break;
1525       }
1526
1527       if (aa.annotations[column] == null)
1528       {
1529         x++;
1530         continue;
1531       }
1532       if (aa.annotations[column].colour == null)
1533         g.setColor(Color.black);
1534       else
1535         g.setColor(aa.annotations[column].colour);
1536
1537       y1 = y
1538               - (int) (((aa.annotations[column].value - min) / (range)) * aa.graphHeight);
1539
1540       if (renderHistogram)
1541       {
1542         if (y1 - y2 > 0)
1543         {
1544           g.fillRect(x * av.charWidth, y2, av.charWidth, y1 - y2);
1545         }
1546         else
1547         {
1548           g.fillRect(x * av.charWidth, y1, av.charWidth, y2 - y1);
1549         }
1550       }
1551       // draw profile if available
1552       if (renderProfile && aa.annotations[column].value != 0)
1553       {
1554
1555         int profl[] = getProfileFor(aa, column);
1556         // just try to draw the logo if profl is not null
1557         if (profl != null)
1558         {
1559
1560           int ht = y1, htn = y2 - y1;// aa.graphHeight;
1561           float wdth;
1562           double ht2 = 0;
1563           char[] dc;
1564
1565           /**
1566            * profl.length == 51 indicates that the profile of a secondary
1567            * structure conservation row was accesed.
1568            * Therefore dc gets length 2, to have space for a basepair instead of
1569            * just a single nucleotide
1570            */
1571           if (profl.length == 51)
1572           {
1573             dc = new char[2];
1574           }
1575           else
1576           {
1577             dc = new char[1];
1578           }
1579
1580           LineMetrics lm;
1581           for (int c = 1; profl != null && c < profl[0];)
1582           {
1583             dc[0] = (char) profl[c++];
1584
1585             if (aa.label.startsWith("StrucConsensus"))
1586             {
1587               dc[1] = (char) profl[c++];
1588             }
1589             
1590             wdth = av.charWidth;
1591             wdth /= (float) fm.charsWidth(dc, 0, dc.length);
1592
1593             if (c > 2)
1594             {
1595               ht += (int) ht2;
1596             }
1597             {
1598               // if (aa.annotations[column].value==0) {
1599               // g.setFont(ofont.deriveFont(AffineTransform.getScaleInstance(wdth,
1600               // (ht2=(aa.graphHeight*0.1/av.charHeight)))));
1601               // ht = y2-(int)ht2;
1602               // } else {
1603               g.setFont(ofont.deriveFont(AffineTransform.getScaleInstance(
1604                       wdth, (ht2 = (htn * ((double) profl[c++]) / 100.0))
1605                               / av.charHeight)));
1606               lm = g.getFontMetrics().getLineMetrics(dc, 0, 1, g);
1607               // htn -=ht2;
1608               // }
1609               g.setColor(profcolour.findColour(dc[0])); // (av.globalColourScheme!=null)
1610               // ? );// try to get a
1611               // colourscheme for the
1612               // group(aa.groupRef.cs==null)
1613               // ? av.textColour2 :
1614               // cs.findColour(dc));
1615               // System.out.println(dc[0]);
1616
1617               g.drawChars(dc, 0, dc.length, x * av.charWidth,
1618                       (int) (ht + lm.getHeight()));
1619
1620               // ht+=g.getFontMetrics().getAscent()-g.getFontMetrics().getDescent();
1621             }
1622           }
1623           g.setFont(ofont);
1624         }
1625       }
1626       x++;
1627     }
1628     if (aa.threshold != null)
1629     {
1630       g.setColor(aa.threshold.colour);
1631       Graphics2D g2 = (Graphics2D) g;
1632       g2.setStroke(new BasicStroke(1, BasicStroke.CAP_SQUARE,
1633               BasicStroke.JOIN_ROUND, 3f, new float[]
1634               { 5f, 3f }, 0f));
1635
1636       y2 = (int) (y - ((aa.threshold.value - min) / range) * aa.graphHeight);
1637       g.drawLine(0, y2, (eRes - sRes) * av.charWidth, y2);
1638       g2.setStroke(new BasicStroke());
1639     }
1640   }
1641
1642   private int[] getProfileFor(AlignmentAnnotation aa, int column)
1643   {
1644     if (aa.autoCalculated && aa.label.startsWith("Consensus"))
1645     {
1646       if (aa.groupRef != null && aa.groupRef.consensusData != null
1647               && aa.groupRef.isShowSequenceLogo())
1648       {
1649         return AAFrequency.extractProfile(
1650                 aa.groupRef.consensusData[column], aa.groupRef
1651                         .getIgnoreGapsConsensus());
1652       }
1653       // TODO extend annotation row to enable dynamic and static profile data to
1654       // be stored
1655       if (aa.groupRef == null && aa.sequenceRef == null
1656               && av.isShowSequenceLogo())
1657       {
1658         return AAFrequency.extractProfile(av.hconsensus[column], av
1659                 .getIgnoreGapsConsensus());
1660       }
1661     }
1662     else
1663     {
1664       if (aa.autoCalculated && aa.label.startsWith("StrucConsensus"))
1665       {
1666         if (aa.groupRef != null && aa.groupRef.consensusData != null
1667                 && aa.groupRef.isShowSequenceLogo())
1668         {
1669           //TODO check what happens for group selections
1670           return StructureFrequency.extractProfile(
1671                   aa.groupRef.consensusData[column], aa.groupRef
1672                           .getIgnoreGapsConsensus());
1673         }
1674         // TODO extend annotation row to enable dynamic and static profile data
1675         // to
1676         // be stored
1677         if (aa.groupRef == null && aa.sequenceRef == null
1678                 && av.isShowSequenceLogo())
1679         {
1680           return StructureFrequency.extractProfile(av.hStrucConsensus[column],
1681                   av.getIgnoreGapsConsensus());
1682         }
1683       }
1684     }
1685     return null;
1686   }
1687
1688   // used by overview window
1689   public void drawGraph(Graphics g, AlignmentAnnotation aa, int width,
1690           int y, int sRes, int eRes)
1691   {
1692     eRes = Math.min(eRes, aa.annotations.length);
1693     g.setColor(Color.white);
1694     g.fillRect(0, 0, width, y);
1695     g.setColor(new Color(0, 0, 180));
1696
1697     int x = 0, height;
1698
1699     for (int j = sRes; j < eRes; j++)
1700     {
1701       if (aa.annotations[j] != null)
1702       {
1703         if (aa.annotations[j].colour == null)
1704           g.setColor(Color.black);
1705         else
1706           g.setColor(aa.annotations[j].colour);
1707
1708         height = (int) ((aa.annotations[j].value / aa.graphMax) * y);
1709         if (height > y)
1710         {
1711           height = y;
1712         }
1713
1714         g.fillRect(x, y - height, av.charWidth, height);
1715       }
1716       x += av.charWidth;
1717     }
1718   }
1719
1720 }