JAL-98 ProfilesI container for profiles for columns
[jalview.git] / src / jalview / renderer / AnnotationRenderer.java
index d5b2f49..a0e530c 100644 (file)
@@ -1,6 +1,6 @@
 /*
 /*
- * Jalview - A Sequence Alignment Editor and Viewer (Version 2.8.2)
- * Copyright (C) 2014 The Jalview Authors
+ * Jalview - A Sequence Alignment Editor and Viewer ($$Version-Rel$$)
+ * Copyright (C) $$Year-Rel$$ The Jalview Authors
  * 
  * This file is part of Jalview.
  * 
  * 
  * This file is part of Jalview.
  * 
 package jalview.renderer;
 
 import jalview.analysis.AAFrequency;
 package jalview.renderer;
 
 import jalview.analysis.AAFrequency;
+import jalview.analysis.CodingUtils;
+import jalview.analysis.Rna;
 import jalview.analysis.StructureFrequency;
 import jalview.api.AlignViewportI;
 import jalview.datamodel.AlignmentAnnotation;
 import jalview.datamodel.Annotation;
 import jalview.datamodel.ColumnSelection;
 import jalview.analysis.StructureFrequency;
 import jalview.api.AlignViewportI;
 import jalview.datamodel.AlignmentAnnotation;
 import jalview.datamodel.Annotation;
 import jalview.datamodel.ColumnSelection;
+import jalview.datamodel.ProfilesI;
 import jalview.schemes.ColourSchemeI;
 import jalview.schemes.ResidueProperties;
 import jalview.schemes.ColourSchemeI;
 import jalview.schemes.ResidueProperties;
+import jalview.util.Platform;
 
 import java.awt.BasicStroke;
 import java.awt.Color;
 
 import java.awt.BasicStroke;
 import java.awt.Color;
@@ -42,88 +46,18 @@ import java.awt.image.ImageObserver;
 import java.util.BitSet;
 import java.util.Hashtable;
 
 import java.util.BitSet;
 import java.util.Hashtable;
 
-import com.stevesoft.pat.Regex;
-
 public class AnnotationRenderer
 {
 public class AnnotationRenderer
 {
-  /**
-   * flag indicating if timing and redraw parameter info should be output
-   */
-  private final boolean debugRedraw;
+  private static final int UPPER_TO_LOWER = 'a' - 'A'; // 32
 
 
-  public AnnotationRenderer()
-  {
-    this(false);
-  }
+  private static final int CHAR_A = 'A'; // 65
+
+  private static final int CHAR_Z = 'Z'; // 90
 
   /**
 
   /**
-   * Create a new annotation Renderer
-   * 
-   * @param debugRedraw
-   *          flag indicating if timing and redraw parameter info should be
-   *          output
+   * flag indicating if timing and redraw parameter info should be output
    */
    */
-  public AnnotationRenderer(boolean debugRedraw)
-  {
-    this.debugRedraw = debugRedraw;
-  }
-
-  public void drawStemAnnot(Graphics g, Annotation[] row_annotations,
-          int lastSSX, int x, int y, int iconOffset, int startRes,
-          int column, boolean validRes, boolean validEnd)
-  {
-    g.setColor(STEM_COLOUR);
-    int sCol = (lastSSX / charWidth) + startRes;
-    int x1 = lastSSX;
-    int x2 = (x * charWidth);
-    Regex closeparen = new Regex("(\\))");
-
-    char dc = (column == 0 || row_annotations[column - 1] == null) ? ' '
-            : row_annotations[column - 1].secondaryStructure;
-
-    boolean diffupstream = sCol == 0 || row_annotations[sCol - 1] == null
-            || dc != row_annotations[sCol - 1].secondaryStructure;
-    boolean diffdownstream = !validRes || !validEnd
-            || row_annotations[column] == null
-            || dc != row_annotations[column].secondaryStructure;
-    // System.out.println("Column "+column+" diff up: "+diffupstream+" down:"+diffdownstream);
-    // If a closing base pair half of the stem, display a backward arrow
-    if (column > 0 && ResidueProperties.isCloseParenRNA(dc))
-    {
-
-      if (diffupstream)
-      // if (validRes && column>1 && row_annotations[column-2]!=null &&
-      // dc.equals(row_annotations[column-2].displayCharacter))
-      {
-        g.fillPolygon(new int[]
-        { lastSSX + 5, lastSSX + 5, lastSSX }, new int[]
-        { y + iconOffset, y + 14 + iconOffset, y + 8 + iconOffset }, 3);
-        x1 += 5;
-      }
-      if (diffdownstream)
-      {
-        x2 -= 1;
-      }
-    }
-    else
-    {
-
-      // display a forward arrow
-      if (diffdownstream)
-      {
-        g.fillPolygon(new int[]
-        { x2 - 5, x2 - 5, x2 }, new int[]
-        { y + iconOffset, y + 14 + iconOffset, y + 8 + iconOffset }, 3);
-        x2 -= 5;
-      }
-      if (diffupstream)
-      {
-        x1 += 1;
-      }
-    }
-    // draw arrow body
-    g.fillRect(x1, y + 4 + iconOffset, x2 - x1, 7);
-  }
+  private final boolean debugRedraw;
 
   private int charWidth, endRes, charHeight;
 
 
   private int charWidth, endRes, charHeight;
 
@@ -131,7 +65,7 @@ public class AnnotationRenderer
 
   private FontMetrics fm;
 
 
   private FontMetrics fm;
 
-  private final boolean MAC = new jalview.util.Platform().isAMac();
+  private final boolean MAC = Platform.isAMac();
 
   boolean av_renderHistogram = true, av_renderProfile = true,
           av_normaliseProfile = false;
 
   boolean av_renderHistogram = true, av_renderProfile = true,
           av_normaliseProfile = false;
@@ -140,7 +74,9 @@ public class AnnotationRenderer
 
   private ColumnSelection columnSelection;
 
 
   private ColumnSelection columnSelection;
 
-  private Hashtable[] hconsensus;
+  private ProfilesI hconsensus;
+
+  private Hashtable[] complementConsensus;
 
   private Hashtable[] hStrucConsensus;
 
 
   private Hashtable[] hStrucConsensus;
 
@@ -187,7 +123,96 @@ public class AnnotationRenderer
    */
   private boolean canClip = false;
 
    */
   private boolean canClip = false;
 
-  public void drawNotCanonicalAnnot(Graphics g, Color nonCanColor,
+  public AnnotationRenderer()
+  {
+    this(false);
+  }
+
+  /**
+   * Create a new annotation Renderer
+   * 
+   * @param debugRedraw
+   *          flag indicating if timing and redraw parameter info should be
+   *          output
+   */
+  public AnnotationRenderer(boolean debugRedraw)
+  {
+    this.debugRedraw = debugRedraw;
+  }
+
+  /**
+   * Remove any references and resources when this object is no longer required
+   */
+  public void dispose()
+  {
+    hconsensus = null;
+    complementConsensus = null;
+    hStrucConsensus = null;
+    fadedImage = null;
+    annotationPanel = null;
+  }
+
+  void drawStemAnnot(Graphics g, Annotation[] row_annotations, int lastSSX,
+          int x, int y, int iconOffset, int startRes, int column,
+          boolean validRes, boolean validEnd)
+  {
+    g.setColor(STEM_COLOUR);
+    int sCol = (lastSSX / charWidth) + startRes;
+    int x1 = lastSSX;
+    int x2 = (x * charWidth);
+
+    char dc = (column == 0 || row_annotations[column - 1] == null) ? ' '
+            : row_annotations[column - 1].secondaryStructure;
+
+    boolean diffupstream = sCol == 0 || row_annotations[sCol - 1] == null
+            || dc != row_annotations[sCol - 1].secondaryStructure;
+    boolean diffdownstream = !validRes || !validEnd
+            || row_annotations[column] == null
+            || dc != row_annotations[column].secondaryStructure;
+
+    if (column > 0 && Rna.isClosingParenthesis(dc))
+    {
+      if (diffupstream)
+      // if (validRes && column>1 && row_annotations[column-2]!=null &&
+      // dc.equals(row_annotations[column-2].displayCharacter))
+      {
+        /*
+         * if new annotation with a closing base pair half of the stem, 
+         * display a backward arrow
+         */
+        g.fillPolygon(new int[] { lastSSX + 5, lastSSX + 5, lastSSX },
+                new int[] { y + iconOffset, y + 14 + iconOffset,
+                    y + 8 + iconOffset }, 3);
+        x1 += 5;
+      }
+      if (diffdownstream)
+      {
+        x2 -= 1;
+      }
+    }
+    else
+    {
+      // display a forward arrow
+      if (diffdownstream)
+      {
+        /*
+         * if annotation ending with an opeing base pair half of the stem, 
+         * display a forward arrow
+         */
+        g.fillPolygon(new int[] { x2 - 5, x2 - 5, x2 }, new int[] {
+            y + iconOffset, y + 14 + iconOffset, y + 8 + iconOffset }, 3);
+        x2 -= 5;
+      }
+      if (diffupstream)
+      {
+        x1 += 1;
+      }
+    }
+    // draw arrow body
+    g.fillRect(x1, y + 4 + iconOffset, x2 - x1, 7);
+  }
+
+  void drawNotCanonicalAnnot(Graphics g, Color nonCanColor,
           Annotation[] row_annotations, int lastSSX, int x, int y,
           int iconOffset, int startRes, int column, boolean validRes,
           boolean validEnd)
           Annotation[] row_annotations, int lastSSX, int x, int y,
           int iconOffset, int startRes, int column, boolean validRes,
           boolean validEnd)
@@ -198,7 +223,6 @@ public class AnnotationRenderer
     int sCol = (lastSSX / charWidth) + startRes;
     int x1 = lastSSX;
     int x2 = (x * charWidth);
     int sCol = (lastSSX / charWidth) + startRes;
     int x1 = lastSSX;
     int x2 = (x * charWidth);
-    Regex closeparen = new Regex("}|]|<|[a-z]");
 
     String dc = (column == 0 || row_annotations[column - 1] == null) ? ""
             : row_annotations[column - 1].displayCharacter;
 
     String dc = (column == 0 || row_annotations[column - 1] == null) ? ""
             : row_annotations[column - 1].displayCharacter;
@@ -210,17 +234,16 @@ public class AnnotationRenderer
             || !dc.equals(row_annotations[column].displayCharacter);
     // System.out.println("Column "+column+" diff up: "+diffupstream+" down:"+diffdownstream);
     // If a closing base pair half of the stem, display a backward arrow
             || !dc.equals(row_annotations[column].displayCharacter);
     // System.out.println("Column "+column+" diff up: "+diffupstream+" down:"+diffdownstream);
     // If a closing base pair half of the stem, display a backward arrow
-    if (column > 0 && closeparen.search(dc))// closeletter_b.search(dc)||closeletter_c.search(dc)||closeletter_d.search(dc)||closecrochet.search(dc))
-                                            // )
+    if (column > 0 && Rna.isClosingParenthesis(dc))
     {
 
       if (diffupstream)
       // if (validRes && column>1 && row_annotations[column-2]!=null &&
       // dc.equals(row_annotations[column-2].displayCharacter))
       {
     {
 
       if (diffupstream)
       // if (validRes && column>1 && row_annotations[column-2]!=null &&
       // dc.equals(row_annotations[column-2].displayCharacter))
       {
-        g.fillPolygon(new int[]
-        { lastSSX + 5, lastSSX + 5, lastSSX }, new int[]
-        { y + iconOffset, y + 14 + iconOffset, y + 8 + iconOffset }, 3);
+        g.fillPolygon(new int[] { lastSSX + 5, lastSSX + 5, lastSSX },
+                new int[] { y + iconOffset, y + 14 + iconOffset,
+                    y + 8 + iconOffset }, 3);
         x1 += 5;
       }
       if (diffdownstream)
         x1 += 5;
       }
       if (diffdownstream)
@@ -234,9 +257,8 @@ public class AnnotationRenderer
       // display a forward arrow
       if (diffdownstream)
       {
       // display a forward arrow
       if (diffdownstream)
       {
-        g.fillPolygon(new int[]
-        { x2 - 5, x2 - 5, x2 }, new int[]
-        { y + iconOffset, y + 14 + iconOffset, y + 8 + iconOffset }, 3);
+        g.fillPolygon(new int[] { x2 - 5, x2 - 5, x2 }, new int[] {
+            y + iconOffset, y + 14 + iconOffset, y + 8 + iconOffset }, 3);
         x2 -= 5;
       }
       if (diffupstream)
         x2 -= 5;
       }
       if (diffupstream)
@@ -298,34 +320,56 @@ public class AnnotationRenderer
       profcolour = av.getAlignment().isNucleotide() ? new jalview.schemes.NucleotideColourScheme()
               : new jalview.schemes.ZappoColourScheme();
     }
       profcolour = av.getAlignment().isNucleotide() ? new jalview.schemes.NucleotideColourScheme()
               : new jalview.schemes.ZappoColourScheme();
     }
-    boolean rna = av.getAlignment().isNucleotide();
     columnSelection = av.getColumnSelection();
     columnSelection = av.getColumnSelection();
-    hconsensus = av.getSequenceConsensusHash();// hconsensus;
-    hStrucConsensus = av.getRnaStructureConsensusHash(); // hStrucConsensus;
-    av_ignoreGapsConsensus = av.getIgnoreGapsConsensus();
+    hconsensus = av.getSequenceConsensusHash();
+    complementConsensus = av.getComplementConsensusHash();
+    hStrucConsensus = av.getRnaStructureConsensusHash();
+    av_ignoreGapsConsensus = av.isIgnoreGapsConsensus();
   }
 
   }
 
-  public int[] getProfileFor(AlignmentAnnotation aa, int column)
+  /**
+   * Returns profile data; the first element is the profile type, the second is
+   * the number of distinct values, the third the total count, and the remainder
+   * depend on the profile type.
+   * 
+   * @param aa
+   * @param column
+   * @return
+   */
+  int[] getProfileFor(AlignmentAnnotation aa, int column)
   {
     // TODO : consider refactoring the global alignment calculation
     // properties/rendering attributes as a global 'alignment group' which holds
     // all vis settings for the alignment as a whole rather than a subset
     //
   {
     // TODO : consider refactoring the global alignment calculation
     // properties/rendering attributes as a global 'alignment group' which holds
     // all vis settings for the alignment as a whole rather than a subset
     //
-    if (aa.autoCalculated && aa.label.startsWith("Consensus"))
+    if (aa.autoCalculated
+            && (aa.label.startsWith("Consensus") || aa.label
+                    .startsWith("cDNA Consensus")))
     {
     {
+      boolean forComplement = aa.label.startsWith("cDNA Consensus");
       if (aa.groupRef != null && aa.groupRef.consensusData != null
               && aa.groupRef.isShowSequenceLogo())
       {
       if (aa.groupRef != null && aa.groupRef.consensusData != null
               && aa.groupRef.isShowSequenceLogo())
       {
+        // TODO? group consensus for cDNA complement
         return AAFrequency.extractProfile(
         return AAFrequency.extractProfile(
-                aa.groupRef.consensusData[column],
+                aa.groupRef.consensusData.get(column),
                 aa.groupRef.getIgnoreGapsConsensus());
       }
       // TODO extend annotation row to enable dynamic and static profile data to
       // be stored
       if (aa.groupRef == null && aa.sequenceRef == null)
       {
                 aa.groupRef.getIgnoreGapsConsensus());
       }
       // TODO extend annotation row to enable dynamic and static profile data to
       // be stored
       if (aa.groupRef == null && aa.sequenceRef == null)
       {
-        return AAFrequency.extractProfile(hconsensus[column],
-                av_ignoreGapsConsensus);
+        if (forComplement)
+        {
+          return AAFrequency.extractCdnaProfile(
+                  complementConsensus[column], av_ignoreGapsConsensus);
+        }
+        else
+        {
+          return AAFrequency.extractProfile(
+hconsensus.get(column),
+                  av_ignoreGapsConsensus);
+        }
       }
     }
     else
       }
     }
     else
@@ -400,12 +444,15 @@ public class AnnotationRenderer
     boolean validRes = false;
     boolean validEnd = false;
     boolean labelAllCols = false;
     boolean validRes = false;
     boolean validEnd = false;
     boolean labelAllCols = false;
-    boolean centreColLabels, centreColLabelsDef = av
-            .getCentreColumnLabels();
+    boolean centreColLabels;
+    boolean centreColLabelsDef = av.isCentreColumnLabels();
     boolean scaleColLabel = false;
     boolean scaleColLabel = false;
-    AlignmentAnnotation consensusAnnot = av
-            .getAlignmentConsensusAnnotation(), structConsensusAnnot = av
+    final AlignmentAnnotation consensusAnnot = av
+            .getAlignmentConsensusAnnotation();
+    final AlignmentAnnotation structConsensusAnnot = av
             .getAlignmentStrucConsensusAnnotation();
             .getAlignmentStrucConsensusAnnotation();
+    final AlignmentAnnotation complementConsensusAnnot = av
+            .getComplementConsensusAnnotation();
     boolean renderHistogram = true, renderProfile = true, normaliseProfile = false, isRNA = rna;
 
     BitSet graphGroupDrawn = new BitSet();
     boolean renderHistogram = true, renderProfile = true, normaliseProfile = false, isRNA = rna;
 
     BitSet graphGroupDrawn = new BitSet();
@@ -432,7 +479,8 @@ public class AnnotationRenderer
           renderProfile = row.groupRef.isShowSequenceLogo();
           normaliseProfile = row.groupRef.isNormaliseSequenceLogo();
         }
           renderProfile = row.groupRef.isShowSequenceLogo();
           normaliseProfile = row.groupRef.isNormaliseSequenceLogo();
         }
-        else if (row == consensusAnnot || row == structConsensusAnnot)
+        else if (row == consensusAnnot || row == structConsensusAnnot
+                || row == complementConsensusAnnot)
         {
           renderHistogram = av_renderHistogram;
           renderProfile = av_renderProfile;
         {
           renderHistogram = av_renderHistogram;
           renderProfile = av_renderProfile;
@@ -556,6 +604,8 @@ public class AnnotationRenderer
           {
             validRes = true;
           }
           {
             validRes = true;
           }
+          final String displayChar = validRes ? row_annotations[column].displayCharacter
+                  : null;
           if (x > -1)
           {
             if (activeRow == i)
           if (x > -1)
           {
             if (activeRow == i)
@@ -564,14 +614,9 @@ public class AnnotationRenderer
 
               if (columnSelection != null)
               {
 
               if (columnSelection != null)
               {
-                for (int n = 0; n < columnSelection.size(); n++)
+                if (columnSelection.contains(column))
                 {
                 {
-                  int v = columnSelection.columnAt(n);
-
-                  if (v == column)
-                  {
-                    g.fillRect(x * charWidth, y, charWidth, charHeight);
-                  }
+                  g.fillRect(x * charWidth, y, charWidth, charHeight);
                 }
               }
             }
                 }
               }
             }
@@ -585,40 +630,37 @@ public class AnnotationRenderer
               g.setColor(Color.orange.darker());
               g.fillRect(x * charWidth, y, charWidth, charHeight);
             }
               g.setColor(Color.orange.darker());
               g.fillRect(x * charWidth, y, charWidth, charHeight);
             }
-            if (validCharWidth
-                    && validRes
-                    && row_annotations[column].displayCharacter != null
-                    && (row_annotations[column].displayCharacter.length() > 0))
+            if (validCharWidth && validRes && displayChar != null
+                    && (displayChar.length() > 0))
             {
 
             {
 
-              if (centreColLabels || scaleColLabel)
+              fmWidth = fm.charsWidth(displayChar.toCharArray(), 0,
+                      displayChar.length());
+              if (/* centreColLabels || */scaleColLabel)
               {
               {
-                fmWidth = fm.charsWidth(
-                        row_annotations[column].displayCharacter
-                                .toCharArray(), 0,
-                        row_annotations[column].displayCharacter.length());
-
-                if (scaleColLabel)
+                // fmWidth = fm.charsWidth(displayChar.toCharArray(), 0,
+                // displayChar.length());
+                //
+                // if (scaleColLabel)
+                // {
+                // justify the label and scale to fit in column
+                if (fmWidth > charWidth)
                 {
                 {
-                  // justify the label and scale to fit in column
-                  if (fmWidth > charWidth)
-                  {
-                    // scale only if the current font isn't already small enough
-                    fmScaling = charWidth;
-                    fmScaling /= fmWidth;
-                    g.setFont(ofont.deriveFont(AffineTransform
-                            .getScaleInstance(fmScaling, 1.0)));
-                    // and update the label's width to reflect the scaling.
-                    fmWidth = charWidth;
-                  }
+                  // scale only if the current font isn't already small enough
+                  fmScaling = charWidth;
+                  fmScaling /= fmWidth;
+                  g.setFont(ofont.deriveFont(AffineTransform
+                          .getScaleInstance(fmScaling, 1.0)));
+                  // and update the label's width to reflect the scaling.
+                  fmWidth = charWidth;
                 }
                 }
+                // }
               }
               }
-              else
-              {
-                fmWidth = fm
-                        .charWidth(row_annotations[column].displayCharacter
-                                .charAt(0));
-              }
+              // TODO is it ok to use width of / show all characters here?
+              // else
+              // {
+              // fmWidth = fm.charWidth(displayChar.charAt(0));
+              // }
               charOffset = (int) ((charWidth - fmWidth) / 2f);
 
               if (row_annotations[column].colour == null)
               charOffset = (int) ((charWidth - fmWidth) / 2f);
 
               if (row_annotations[column].colour == null)
@@ -632,17 +674,17 @@ public class AnnotationRenderer
 
               if (column == 0 || row.graph > 0)
               {
 
               if (column == 0 || row.graph > 0)
               {
-                g.drawString(row_annotations[column].displayCharacter,
-                        (x * charWidth) + charOffset, y + iconOffset);
+                g.drawString(displayChar, (x * charWidth) + charOffset, y
+                        + iconOffset);
               }
               else if (row_annotations[column - 1] == null
                       || (labelAllCols
               }
               else if (row_annotations[column - 1] == null
                       || (labelAllCols
-                              || !row_annotations[column].displayCharacter
-                                      .equals(row_annotations[column - 1].displayCharacter) || (row_annotations[column].displayCharacter
+                              || !displayChar
+                                      .equals(row_annotations[column - 1].displayCharacter) || (displayChar
                               .length() < 2 && row_annotations[column].secondaryStructure == ' ')))
               {
                               .length() < 2 && row_annotations[column].secondaryStructure == ' ')))
               {
-                g.drawString(row_annotations[column].displayCharacter, x
-                        * charWidth + charOffset, y + iconOffset);
+                g.drawString(displayChar, x * charWidth + charOffset, y
+                        + iconOffset);
               }
               g.setFont(ofont);
             }
               }
               g.setFont(ofont);
             }
@@ -655,7 +697,7 @@ public class AnnotationRenderer
             if (ss == '(')
             {
               // distinguish between forward/backward base-pairing
             if (ss == '(')
             {
               // distinguish between forward/backward base-pairing
-              if (row_annotations[column].displayCharacter.indexOf(')') > -1)
+              if (displayChar.indexOf(')') > -1)
               {
 
                 ss = ')';
               {
 
                 ss = ')';
@@ -664,7 +706,7 @@ public class AnnotationRenderer
             }
             if (ss == '[')
             {
             }
             if (ss == '[')
             {
-              if ((row_annotations[column].displayCharacter.indexOf(']') > -1))
+              if ((displayChar.indexOf(']') > -1))
               {
                 ss = ']';
 
               {
                 ss = ']';
 
@@ -673,7 +715,7 @@ public class AnnotationRenderer
             if (ss == '{')
             {
               // distinguish between forward/backward base-pairing
             if (ss == '{')
             {
               // distinguish between forward/backward base-pairing
-              if (row_annotations[column].displayCharacter.indexOf('}') > -1)
+              if (displayChar.indexOf('}') > -1)
               {
                 ss = '}';
 
               {
                 ss = '}';
 
@@ -682,20 +724,20 @@ public class AnnotationRenderer
             if (ss == '<')
             {
               // distinguish between forward/backward base-pairing
             if (ss == '<')
             {
               // distinguish between forward/backward base-pairing
-              if (row_annotations[column].displayCharacter.indexOf('<') > -1)
+              if (displayChar.indexOf('<') > -1)
               {
                 ss = '>';
 
               }
             }
               {
                 ss = '>';
 
               }
             }
-            if (ss >= 65)
+            if (isRNA && (ss >= CHAR_A) && (ss <= CHAR_Z))
             {
               // distinguish between forward/backward base-pairing
             {
               // distinguish between forward/backward base-pairing
-              if (row_annotations[column].displayCharacter.indexOf(ss + 32) > -1)
+              int ssLowerCase = ss + UPPER_TO_LOWER;
+              // TODO would .equals() be safer here? or charAt(0)?
+              if (displayChar.indexOf(ssLowerCase) > -1)
               {
               {
-
-                ss = (char) (ss + 32);
-
+                ss = (char) ssLowerCase;
               }
             }
 
               }
             }
 
@@ -724,7 +766,7 @@ public class AnnotationRenderer
                             validEnd);
                     break;
                   }
                             validEnd);
                     break;
                   }
-
+                  // no break if isRNA - falls through to drawNotCanonicalAnnot!
                 case 'E':
                   if (!isRNA)
                   {
                 case 'E':
                   if (!isRNA)
                   {
@@ -733,6 +775,7 @@ public class AnnotationRenderer
                             validEnd);
                     break;
                   }
                             validEnd);
                     break;
                   }
+                  // no break if isRNA - fall through to drawNotCanonicalAnnot!
 
                 case '{':
                 case '}':
 
                 case '{':
                 case '}':
@@ -840,7 +883,6 @@ public class AnnotationRenderer
         {
           validRes = true;
         }
         {
           validRes = true;
         }
-
         // x ++;
 
         if (row.hasIcons)
         // x ++;
 
         if (row.hasIcons)
@@ -855,6 +897,7 @@ public class AnnotationRenderer
                       startRes, column, validRes, validEnd);
               break;
             }
                       startRes, column, validRes, validEnd);
               break;
             }
+            // no break if isRNA - fall through to drawNotCanonicalAnnot!
 
           case 'E':
             if (!isRNA)
 
           case 'E':
             if (!isRNA)
@@ -863,6 +906,7 @@ public class AnnotationRenderer
                       startRes, column, validRes, validEnd);
               break;
             }
                       startRes, column, validRes, validEnd);
               break;
             }
+            // no break if isRNA - fall through to drawNotCanonicalAnnot!
 
           case '(':
           case ')': // Stem case for RNA secondary structure
 
           case '(':
           case ')': // Stem case for RNA secondary structure
@@ -1043,15 +1087,15 @@ public class AnnotationRenderer
 
   private Color sdNOTCANONICAL_COLOUR;
 
 
   private Color sdNOTCANONICAL_COLOUR;
 
-  public void drawGlyphLine(Graphics g, Annotation[] row, int lastSSX,
-          int x, int y, int iconOffset, int startRes, int column,
+  void drawGlyphLine(Graphics g, Annotation[] row, int lastSSX, int x,
+          int y, int iconOffset, int startRes, int column,
           boolean validRes, boolean validEnd)
   {
     g.setColor(GLYPHLINE_COLOR);
     g.fillRect(lastSSX, y + 6 + iconOffset, (x * charWidth) - lastSSX, 2);
   }
 
           boolean validRes, boolean validEnd)
   {
     g.setColor(GLYPHLINE_COLOR);
     g.fillRect(lastSSX, y + 6 + iconOffset, (x * charWidth) - lastSSX, 2);
   }
 
-  public void drawSheetAnnot(Graphics g, Annotation[] row,
+  void drawSheetAnnot(Graphics g, Annotation[] row,
 
   int lastSSX, int x, int y, int iconOffset, int startRes, int column,
           boolean validRes, boolean validEnd)
 
   int lastSSX, int x, int y, int iconOffset, int startRes, int column,
           boolean validRes, boolean validEnd)
@@ -1063,11 +1107,9 @@ public class AnnotationRenderer
     {
       g.fillRect(lastSSX, y + 4 + iconOffset,
               (x * charWidth) - lastSSX - 4, 7);
     {
       g.fillRect(lastSSX, y + 4 + iconOffset,
               (x * charWidth) - lastSSX - 4, 7);
-      g.fillPolygon(new int[]
-      { (x * charWidth) - 4, (x * charWidth) - 4, (x * charWidth) },
-              new int[]
-              { y + iconOffset, y + 14 + iconOffset, y + 7 + iconOffset },
-              3);
+      g.fillPolygon(new int[] { (x * charWidth) - 4, (x * charWidth) - 4,
+          (x * charWidth) }, new int[] { y + iconOffset,
+          y + 14 + iconOffset, y + 7 + iconOffset }, 3);
     }
     else
     {
     }
     else
     {
@@ -1077,8 +1119,8 @@ public class AnnotationRenderer
 
   }
 
 
   }
 
-  public void drawHelixAnnot(Graphics g, Annotation[] row, int lastSSX,
-          int x, int y, int iconOffset, int startRes, int column,
+  void drawHelixAnnot(Graphics g, Annotation[] row, int lastSSX, int x,
+          int y, int iconOffset, int startRes, int column,
           boolean validRes, boolean validEnd)
   {
     g.setColor(HELIX_COLOUR);
           boolean validRes, boolean validEnd)
   {
     g.setColor(HELIX_COLOUR);
@@ -1137,7 +1179,7 @@ public class AnnotationRenderer
     g.fillRect(x1, y + 4 + iconOffset, x2 - x1, 8);
   }
 
     g.fillRect(x1, y + 4 + iconOffset, x2 - x1, 8);
   }
 
-  public void drawLineGraph(Graphics g, AlignmentAnnotation _aa,
+  void drawLineGraph(Graphics g, AlignmentAnnotation _aa,
           Annotation[] aa_annotations, int sRes, int eRes, int y,
           float min, float max, int graphHeight)
   {
           Annotation[] aa_annotations, int sRes, int eRes, int y,
           float min, float max, int graphHeight)
   {
@@ -1222,8 +1264,7 @@ public class AnnotationRenderer
       g.setColor(_aa.threshold.colour);
       Graphics2D g2 = (Graphics2D) g;
       g2.setStroke(new BasicStroke(1, BasicStroke.CAP_SQUARE,
       g.setColor(_aa.threshold.colour);
       Graphics2D g2 = (Graphics2D) g;
       g2.setStroke(new BasicStroke(1, BasicStroke.CAP_SQUARE,
-              BasicStroke.JOIN_ROUND, 3f, new float[]
-              { 5f, 3f }, 0f));
+              BasicStroke.JOIN_ROUND, 3f, new float[] { 5f, 3f }, 0f));
 
       y2 = (int) (y - ((_aa.threshold.value - min) / range) * graphHeight);
       g.drawLine(0, y2, (eRes - sRes) * charWidth, y2);
 
       y2 = (int) (y - ((_aa.threshold.value - min) / range) * graphHeight);
       g.drawLine(0, y2, (eRes - sRes) * charWidth, y2);
@@ -1231,7 +1272,7 @@ public class AnnotationRenderer
     }
   }
 
     }
   }
 
-  public void drawBarGraph(Graphics g, AlignmentAnnotation _aa,
+  void drawBarGraph(Graphics g, AlignmentAnnotation _aa,
           Annotation[] aa_annotations, int sRes, int eRes, float min,
           float max, int y, boolean renderHistogram, boolean renderProfile,
           boolean normaliseProfile)
           Annotation[] aa_annotations, int sRes, int eRes, float min,
           float max, int y, boolean renderHistogram, boolean renderProfile,
           boolean normaliseProfile)
@@ -1303,10 +1344,16 @@ public class AnnotationRenderer
       if (renderProfile)
       {
 
       if (renderProfile)
       {
 
+        /*
+         * {profile type, #values, total count, char1, pct1, char2, pct2...}
+         */
         int profl[] = getProfileFor(_aa, column);
         int profl[] = getProfileFor(_aa, column);
+
         // just try to draw the logo if profl is not null
         // just try to draw the logo if profl is not null
-        if (profl != null && profl[1] != 0)
+        if (profl != null && profl[2] != 0)
         {
         {
+          boolean isStructureProfile = profl[0] == AlignmentAnnotation.STRUCTURE_PROFILE;
+          boolean isCdnaProfile = profl[0] == AlignmentAnnotation.CDNA_PROFILE;
           float ht = normaliseProfile ? y - _aa.graphHeight : y1;
           double htn = normaliseProfile ? _aa.graphHeight : (y2 - y1);// aa.graphHeight;
           double hght;
           float ht = normaliseProfile ? y - _aa.graphHeight : y1;
           double htn = normaliseProfile ? _aa.graphHeight : (y2 - y1);// aa.graphHeight;
           double hght;
@@ -1315,55 +1362,81 @@ public class AnnotationRenderer
           char[] dc;
 
           /**
           char[] dc;
 
           /**
-           * profl.length == 74 indicates that the profile of a secondary
-           * structure conservation row was accesed. Therefore dc gets length 2,
-           * to have space for a basepair instead of just a single nucleotide
+           * Render a single base for a sequence profile, a base pair for
+           * structure profile, and a triplet for a cdna profile
            */
            */
-          if (profl.length == 74)
-          {
-            dc = new char[2];
-          }
-          else
-          {
-            dc = new char[1];
-          }
+          dc = new char[isStructureProfile ? 2 : (isCdnaProfile ? 3 : 1)];
+
           LineMetrics lm = g.getFontMetrics(ofont).getLineMetrics("Q", g);
           LineMetrics lm = g.getFontMetrics(ofont).getLineMetrics("Q", g);
-          double scale = 1f / (normaliseProfile ? profl[1] : 100f);
+          double scale = 1f / (normaliseProfile ? profl[2] : 100f);
           float ofontHeight = 1f / lm.getAscent();// magnify to fill box
           double scl = 0.0;
           float ofontHeight = 1f / lm.getAscent();// magnify to fill box
           double scl = 0.0;
-          for (int c = 2; c < profl[0];)
-          {
-            dc[0] = (char) profl[c++];
 
 
-            if (_aa.label.startsWith("StrucConsensus"))
+          /*
+           * Traverse the character(s)/percentage data in the array
+           */
+          int c = 3;
+          int valuesProcessed = 0;
+          // profl[1] is the number of values in the profile
+          while (valuesProcessed < profl[1])
+          {
+            if (isStructureProfile)
             {
             {
+              // todo can we encode a structure pair as an int, like codons?
+              dc[0] = (char) profl[c++];
               dc[1] = (char) profl[c++];
             }
               dc[1] = (char) profl[c++];
             }
+            else if (isCdnaProfile)
+            {
+              dc = CodingUtils.decodeCodon(profl[c++]);
+            }
+            else
+            {
+              dc[0] = (char) profl[c++];
+            }
 
             wdth = charWidth;
             wdth /= fm.charsWidth(dc, 0, dc.length);
 
             ht += scl;
 
             wdth = charWidth;
             wdth /= fm.charsWidth(dc, 0, dc.length);
 
             ht += scl;
+            // next profl[] position is profile % for the character(s)
+            scl = htn * scale * profl[c++];
+            lm = ofont.getLineMetrics(dc, 0, 1, g.getFontMetrics()
+                    .getFontRenderContext());
+            Font font = ofont.deriveFont(AffineTransform.getScaleInstance(
+                    wdth, scl / lm.getAscent()));
+            g.setFont(font);
+            lm = g.getFontMetrics().getLineMetrics(dc, 0, 1, g);
+
+            // Debug - render boxes around characters
+            // g.setColor(Color.red);
+            // g.drawRect(x*av.charWidth, (int)ht, av.charWidth,
+            // (int)(scl));
+            // g.setColor(profcolour.findColour(dc[0]).darker());
+
+            /*
+             * Set character colour as per alignment colour scheme; use the
+             * codon translation if a cDNA profile
+             */
+            Color colour = null;
+            if (isCdnaProfile)
             {
             {
-              scl = htn * scale * profl[c++];
-              lm = ofont.getLineMetrics(dc, 0, 1, g.getFontMetrics()
-                      .getFontRenderContext());
-              g.setFont(ofont.deriveFont(AffineTransform.getScaleInstance(
-                      wdth, scl / lm.getAscent())));
-              lm = g.getFontMetrics().getLineMetrics(dc, 0, 1, g);
-
-              // Debug - render boxes around characters
-              // g.setColor(Color.red);
-              // g.drawRect(x*av.charWidth, (int)ht, av.charWidth,
-              // (int)(scl));
-              // g.setColor(profcolour.findColour(dc[0]).darker());
-              g.setColor(profcolour.findColour(dc[0], column, null));
-
-              hght = (ht + (scl - lm.getDescent() - lm.getBaselineOffsets()[lm
-                      .getBaselineIndex()]));
-
-              g.drawChars(dc, 0, dc.length, x * charWidth, (int) hght);
+              final String codonTranslation = ResidueProperties
+                      .codonTranslate(new String(dc));
+              colour = profcolour.findColour(codonTranslation.charAt(0),
+                      column, null);
             }
             }
+            else
+            {
+              colour = profcolour.findColour(dc[0], column, null);
+            }
+            g.setColor(colour == Color.white ? Color.lightGray : colour);
+
+            hght = (ht + (scl - lm.getDescent() - lm.getBaselineOffsets()[lm
+                    .getBaselineIndex()]));
+
+            g.drawChars(dc, 0, dc.length, x * charWidth, (int) hght);
+            valuesProcessed++;
           }
           g.setFont(ofont);
         }
           }
           g.setFont(ofont);
         }
@@ -1375,8 +1448,7 @@ public class AnnotationRenderer
       g.setColor(_aa.threshold.colour);
       Graphics2D g2 = (Graphics2D) g;
       g2.setStroke(new BasicStroke(1, BasicStroke.CAP_SQUARE,
       g.setColor(_aa.threshold.colour);
       Graphics2D g2 = (Graphics2D) g;
       g2.setStroke(new BasicStroke(1, BasicStroke.CAP_SQUARE,
-              BasicStroke.JOIN_ROUND, 3f, new float[]
-              { 5f, 3f }, 0f));
+              BasicStroke.JOIN_ROUND, 3f, new float[] { 5f, 3f }, 0f));
 
       y2 = (int) (y - ((_aa.threshold.value - min) / range)
               * _aa.graphHeight);
 
       y2 = (int) (y - ((_aa.threshold.value - min) / range)
               * _aa.graphHeight);