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