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