Merge branch 'develop' into update_212_Dec_merge_with_21125_chamges
[jalview.git] / src / jalview / renderer / AnnotationRenderer.java
index f883b13..7ceb901 100644 (file)
@@ -29,12 +29,14 @@ import jalview.datamodel.AlignmentAnnotation;
 import jalview.datamodel.Annotation;
 import jalview.datamodel.ColumnSelection;
 import jalview.datamodel.HiddenColumns;
+import jalview.datamodel.HiddenMarkovModel;
 import jalview.datamodel.ProfilesI;
 import jalview.schemes.ColourSchemeI;
 import jalview.schemes.NucleotideColourScheme;
 import jalview.schemes.ResidueProperties;
 import jalview.schemes.ZappoColourScheme;
 import jalview.util.Platform;
+import jalview.workers.InformationThread;
 
 import java.awt.BasicStroke;
 import java.awt.Color;
@@ -43,7 +45,6 @@ import java.awt.FontMetrics;
 import java.awt.Graphics;
 import java.awt.Graphics2D;
 import java.awt.Image;
-import java.awt.font.LineMetrics;
 import java.awt.geom.AffineTransform;
 import java.awt.image.ImageObserver;
 import java.util.BitSet;
@@ -68,10 +69,16 @@ public class AnnotationRenderer
 
   private FontMetrics fm;
 
-  private final boolean MAC = Platform.isAMac();
+  private final boolean USE_FILL_ROUND_RECT = Platform.isAMacAndNotJS();
 
-  boolean av_renderHistogram = true, av_renderProfile = true,
-          av_normaliseProfile = false;
+  // todo remove these flags, read from group/viewport where needed
+  boolean av_renderHistogram = true;
+
+  boolean av_renderProfile = true;
+
+  boolean av_normaliseProfile = false;
+
+  boolean av_infoHeight = false;
 
   ResidueShaderI profcolour = null;
 
@@ -81,12 +88,14 @@ public class AnnotationRenderer
 
   private ProfilesI hconsensus;
 
-  private Hashtable[] complementConsensus;
+  private Hashtable<String, Object>[] complementConsensus;
 
-  private Hashtable[] hStrucConsensus;
+  private Hashtable<String, Object>[] hStrucConsensus;
 
   private boolean av_ignoreGapsConsensus;
 
+  private boolean av_ignoreBelowBackground;
+
   /**
    * attributes set from AwtRenderPanelI
    */
@@ -150,6 +159,7 @@ public class AnnotationRenderer
    */
   public void dispose()
   {
+    hiddenColumns = null;
     hconsensus = null;
     complementConsensus = null;
     hStrucConsensus = null;
@@ -345,8 +355,12 @@ public class AnnotationRenderer
     complementConsensus = av.getComplementConsensusHash();
     hStrucConsensus = av.getRnaStructureConsensusHash();
     av_ignoreGapsConsensus = av.isIgnoreGapsConsensus();
+    av_ignoreBelowBackground = av.isIgnoreBelowBackground();
+    av_infoHeight = av.isInfoLetterHeight();
   }
 
+
+
   /**
    * 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
@@ -362,17 +376,25 @@ public class AnnotationRenderer
     // 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")
-            || aa.label.startsWith("cDNA Consensus")))
+    if (InformationThread.HMM_CALC_ID.equals(aa.getCalcId()))
+    {
+      HiddenMarkovModel hmm = aa.sequenceRef.getHMM();
+      return AAFrequency.extractHMMProfile(hmm, column,
+              av_ignoreBelowBackground, av_infoHeight); // TODO check if this follows standard
+                                         // pipeline
+    }
+    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
+      if (aa.groupRef != null && aa.groupRef.getConsensusData() != null
               && aa.groupRef.isShowSequenceLogo())
       {
         // TODO? group consensus for cDNA complement
         return AAFrequency.extractProfile(
-                aa.groupRef.consensusData.get(column),
-                aa.groupRef.getIgnoreGapsConsensus());
+                aa.groupRef.getConsensusData().get(column),
+                                               aa.groupRef.getIgnoreGapsConsensus());
       }
       // TODO extend annotation row to enable dynamic and static profile data to
       // be stored
@@ -417,8 +439,6 @@ public class AnnotationRenderer
     return null;
   }
 
-  boolean rna = false;
-
   /**
    * Render the annotation rows associated with an alignment.
    * 
@@ -449,7 +469,7 @@ public class AnnotationRenderer
     updateFromAwtRenderPanel(annotPanel, av);
     fm = g.getFontMetrics();
     AlignmentAnnotation[] aa = av.getAlignment().getAlignmentAnnotation();
-    int temp = 0;
+    // int temp = 0;
     if (aa == null)
     {
       return false;
@@ -462,8 +482,8 @@ public class AnnotationRenderer
     boolean validRes = false;
     boolean validEnd = false;
     boolean labelAllCols = false;
-    boolean centreColLabels;
-    boolean centreColLabelsDef = av.isCentreColumnLabels();
+    // boolean centreColLabels;
+    // boolean centreColLabelsDef = av.isCentreColumnLabels();
     boolean scaleColLabel = false;
     final AlignmentAnnotation consensusAnnot = av
             .getAlignmentConsensusAnnotation();
@@ -471,14 +491,9 @@ public class AnnotationRenderer
             .getAlignmentStrucConsensusAnnotation();
     final AlignmentAnnotation complementConsensusAnnot = av
             .getComplementConsensusAnnotation();
-    boolean renderHistogram = true, renderProfile = true,
-            normaliseProfile = false, isRNA = rna;
 
     BitSet graphGroupDrawn = new BitSet();
     int charOffset = 0; // offset for a label
-    float fmWidth, fmScaling = 1f; // scaling for a label to fit it into a
-    // column.
-    Font ofont = g.getFont();
     // \u03B2 \u03B1
     // debug ints
     int yfrom = 0, f_i = 0, yto = 0, f_to = 0;
@@ -486,38 +501,57 @@ public class AnnotationRenderer
     for (int i = 0; i < aa.length; i++)
     {
       AlignmentAnnotation row = aa[i];
-      isRNA = row.isRNA();
+      boolean renderHistogram = true;
+      boolean renderProfile = false;
+      boolean normaliseProfile = false;
+      boolean isRNA = row.isRNA();
+
+      // check if this is a consensus annotation row and set the display
+      // settings appropriately
+      // TODO: generalise this to have render styles for consensus/profile
+      // data
+      if (row.groupRef != null && row == row.groupRef.getConsensus())
       {
-        // check if this is a consensus annotation row and set the display
-        // settings appropriately
-        // TODO: generalise this to have render styles for consensus/profile
-        // data
-        if (row.groupRef != null && row == row.groupRef.getConsensus())
-        {
-          renderHistogram = row.groupRef.isShowConsensusHistogram();
-          renderProfile = row.groupRef.isShowSequenceLogo();
-          normaliseProfile = row.groupRef.isNormaliseSequenceLogo();
-        }
-        else if (row == consensusAnnot || row == structConsensusAnnot
-                || row == complementConsensusAnnot)
+        renderHistogram = row.groupRef.isShowConsensusHistogram();
+        renderProfile = row.groupRef.isShowSequenceLogo();
+        normaliseProfile = row.groupRef.isNormaliseSequenceLogo();
+      }
+      else if (row == consensusAnnot || row == structConsensusAnnot
+              || row == complementConsensusAnnot)
+      {
+        renderHistogram = av_renderHistogram;
+        renderProfile = av_renderProfile;
+        normaliseProfile = av_normaliseProfile;
+      }
+      else if (InformationThread.HMM_CALC_ID.equals(row.getCalcId()))
+      {
+        if (row.groupRef != null)
         {
-          renderHistogram = av_renderHistogram;
-          renderProfile = av_renderProfile;
-          normaliseProfile = av_normaliseProfile;
+          renderHistogram = row.groupRef.isShowInformationHistogram();
+          renderProfile = row.groupRef.isShowHMMSequenceLogo();
+          normaliseProfile = row.groupRef.isNormaliseHMMSequenceLogo();
         }
         else
         {
-          renderHistogram = true;
-          // don't need to set render/normaliseProfile since they are not
-          // currently used in any other annotation track renderer
+          renderHistogram = av.isShowInformationHistogram();
+          renderProfile = av.isShowHMMSequenceLogo();
+          normaliseProfile = av.isNormaliseHMMSequenceLogo();
         }
       }
+      else if (row == consensusAnnot || row == structConsensusAnnot
+              || row == complementConsensusAnnot)
+      {
+        renderHistogram = av_renderHistogram;
+        renderProfile = av_renderProfile;
+        normaliseProfile = av_normaliseProfile;
+      }
+
       Annotation[] row_annotations = row.annotations;
       if (!row.visible)
       {
         continue;
       }
-      centreColLabels = row.centreColLabels || centreColLabelsDef;
+      // centreColLabels = row.centreColLabels || centreColLabelsDef;
       labelAllCols = row.showAllColLabels;
       scaleColLabel = row.scaleColLabel;
       lastSS = ' ';
@@ -653,49 +687,59 @@ public class AnnotationRenderer
             if (validCharWidth && validRes && displayChar != null
                     && (displayChar.length() > 0))
             {
-
-              fmWidth = fm.charsWidth(displayChar.toCharArray(), 0,
+              Graphics2D gg = ((Graphics2D) g);
+              float fmWidth = fm.charsWidth(displayChar.toCharArray(), 0,
                       displayChar.length());
-              if (/* centreColLabels || */scaleColLabel)
+
+              /*
+               * shrink label width to fit in column, if that is
+               * both configured and necessary
+               */
+              boolean scaledToFit = false;
+              float fmScaling = 1f;
+              if (scaleColLabel && fmWidth > charWidth)
               {
-                // fmWidth = fm.charsWidth(displayChar.toCharArray(), 0,
-                // displayChar.length());
-                //
-                // if (scaleColLabel)
-                // {
-                // 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;
-                }
-                // }
+                scaledToFit = true;
+                fmScaling = charWidth;
+                fmScaling /= fmWidth;
+                // and update the label's width to reflect the scaling.
+                fmWidth = charWidth;
               }
-              // 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)
               {
-                g.setColor(Color.black);
+                gg.setColor(Color.black);
               }
               else
               {
-                g.setColor(row_annotations[column].colour);
+                gg.setColor(row_annotations[column].colour);
               }
 
+              /*
+               * draw the label, unless it is the same secondary structure
+               * symbol (excluding RNA Helix) as the previous column
+               */
+              final int xPos = (x * charWidth) + charOffset;
+              final int yPos = y + iconOffset;
+
+              /*
+               * translate to drawing position _before_ applying any scaling
+               */
+              gg.translate(xPos, yPos);
+              if (scaledToFit)
+              {
+                /*
+                 * use a scaling transform to make the label narrower
+                 * (JalviewJS doesn't have Font.deriveFont(AffineTransform))
+                 */
+                gg.transform(
+                        AffineTransform.getScaleInstance(fmScaling, 1.0));
+              }
               if (column == 0 || row.graph > 0)
               {
-                g.drawString(displayChar, (x * charWidth) + charOffset,
-                        y + iconOffset);
+                gg.drawString(displayChar, 0, 0);
               }
               else if (row_annotations[column - 1] == null || (labelAllCols
                       || !displayChar.equals(
@@ -703,10 +747,18 @@ public class AnnotationRenderer
                       || (displayChar.length() < 2
                               && row_annotations[column].secondaryStructure == ' ')))
               {
-                g.drawString(displayChar, x * charWidth + charOffset,
-                        y + iconOffset);
+                gg.drawString(displayChar, 0, 0);
               }
-              g.setFont(ofont);
+              if (scaledToFit)
+              {
+                /*
+                 * undo scaling before translating back 
+                 * (restoring saved transform does NOT work in JS PDFGraphics!)
+                 */
+                gg.transform(AffineTransform
+                        .getScaleInstance(1D / fmScaling, 1.0));
+              }
+              gg.translate(-xPos, -yPos);
             }
           }
           if (row.hasIcons)
@@ -767,7 +819,7 @@ public class AnnotationRenderer
               if (x > -1)
               {
 
-                int nb_annot = x - temp;
+                // int nb_annot = x - temp;
                 // System.out.println("\t type :"+lastSS+"\t x :"+x+"\t nbre
                 // annot :"+nb_annot);
                 switch (lastSS)
@@ -776,7 +828,7 @@ public class AnnotationRenderer
                 case ')': // and opposite direction
                   drawStemAnnot(g, row_annotations, lastSSX, x, y,
                           iconOffset, startRes, column, validRes, validEnd);
-                  temp = x;
+                  // temp = x;
                   break;
 
                 case 'H':
@@ -859,13 +911,13 @@ public class AnnotationRenderer
                   drawNotCanonicalAnnot(g, nonCanColor, row_annotations,
                           lastSSX, x, y, iconOffset, startRes, column,
                           validRes, validEnd);
-                  temp = x;
+                  // temp = x;
                   break;
                 default:
                   g.setColor(Color.gray);
                   g.fillRect(lastSSX, y + 6 + iconOffset,
                           (x * charWidth) - lastSSX, 2);
-                  temp = x;
+                  // temp = x;
                   break;
                 }
               }
@@ -1106,7 +1158,7 @@ public class AnnotationRenderer
 
   public static final Color STEM_COLOUR = Color.blue;
 
-  private Color sdNOTCANONICAL_COLOUR;
+  // private Color sdNOTCANONICAL_COLOUR;
 
   void drawGlyphLine(Graphics g, Annotation[] row, int lastSSX, int x,
           int y, int iconOffset, int startRes, int column, boolean validRes,
@@ -1154,7 +1206,7 @@ public class AnnotationRenderer
     int x1 = lastSSX;
     int x2 = (x * charWidth);
 
-    if (MAC)
+    if (USE_FILL_ROUND_RECT)
     {
       int ofs = charWidth / 2;
       // Off by 1 offset when drawing rects and ovals
@@ -1382,7 +1434,8 @@ public class AnnotationRenderer
           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;
+          final double normaliseFactor = normaliseProfile ? _aa.graphHeight
+                  : (y2 - y1);
 
           /**
            * Render a single base for a sequence profile, a base pair for
@@ -1433,8 +1486,14 @@ public class AnnotationRenderer
               s = new String(dc);
             }
             // next profl[] position is profile % for the character(s)
-            
-            double newHeight = htn * scale * profl[c++];
+
+            int percent = profl[c++];
+            if (percent == 0)
+            {
+              // failsafe in case a count rounds down to 0%
+              continue;
+            }
+            double newHeight = normaliseFactor * scale * percent;
 
             /*
              * Set character colour as per alignment colour scheme; use the
@@ -1460,39 +1519,52 @@ public class AnnotationRenderer
             // (int)(scl));
             // g.setColor(profcolour.findColour(dc[0]).darker());
 
-            double sx = 1f * charWidth / fm.charsWidth(dc, 0, dc.length);            
+            double sx = 1f * charWidth / fm.charsWidth(dc, 0, dc.length);
             double sy = newHeight / asc;
-            double newAsc = asc * sy; 
+            double newAsc = asc * sy;
             double newDec = dec * sy;
-            // it is not necessary to recalculated lm for the new font.
+            // it is not necessary to recalculate lm for the new font.
             // note: lm.getBaselineOffsets()[lm.getBaselineIndex()]) must be 0
             // by definition. Was:
-            // int hght = (int) (ht + (newAsc - newDec - lm.getBaselineOffsets()[lm.getBaselineIndex()]));
-
-            // original:
-
-             if (/** @j2sNative false && */ true) {
-               int hght = (int) (ht + (newAsc - newDec)); // Q: why " - newDec " ? (0,0) is on the font baseline, I think
-               Font font = ofont.deriveFont(AffineTransform.getScaleInstance(sx, sy));
-               g.setFont(font);
-               g.drawChars(dc, 0, dc.length, x * charWidth, hght);
-               ht += newHeight;
-             } else {
-              // SwingJS does not implement font.deriveFont() 
-              // this is off by a very small amount. 
-              int hght2 = (int) (ht2 + newAsc);               
-              Graphics2D gg = (Graphics2D) g.create();
-              gg.setFont(ofont);
+            // int hght = (int) (ht + (newAsc - newDec);
+            // - lm.getBaselineOffsets()[lm.getBaselineIndex()]));
+
+            if (Platform.isJS())
+            {
+              /*
+               * SwingJS does not implement font.deriveFont()
+               * so use a scaling transform to draw instead,
+               * this is off by a very small amount
+               */
+              final int hght = (int) (ht2 + (newAsc - newDec));
+              Graphics2D gg = (Graphics2D) g;
+              int xShift = (int) Math.round(x * charWidth / sx);
+              int yShift = (int) Math.round(hght / sy);
               gg.transform(AffineTransform.getScaleInstance(sx, sy));
-              //System.out.println("sx " + sx + " sy " + sy + " " + hght + " " + lm.getDescent() + " " + dec + " " + newDec + " " + lm.getAscent() + " " + asc + " " + newAsc);
-              gg.drawString(s, (int) (x * charWidth / sx),
-                      (int) (hght2 / sy));
-              gg.dispose();
+              gg.drawString(s, xShift, yShift);
+              gg.transform(
+                      AffineTransform.getScaleInstance(1D / sx, 1D / sy));
               ht2 += newHeight;
             }
-            
+            else
+            /**
+             * Java only
+             * 
+             * @j2sIgnore
+             */
+            {
+              // Java ('normal') method is to scale the font to fit
+
+              final int hght = (int) (ht + (newAsc - newDec));
+              Font font = ofont
+                      .deriveFont(AffineTransform.getScaleInstance(sx, sy));
+              g.setFont(font);
+              g.drawChars(dc, 0, dc.length, x * charWidth, hght);
+              g.setFont(ofont);
+
+              ht += newHeight;
+            }
           }
-          g.setFont(ofont);
         }
       }
       x++;
@@ -1503,7 +1575,7 @@ public class AnnotationRenderer
       Graphics2D g2 = (Graphics2D) g;
       g2.setStroke(new BasicStroke(1, BasicStroke.CAP_SQUARE,
               BasicStroke.JOIN_ROUND, 3f, new float[]
-      { 5f, 3f }, 0f));
+              { 5f, 3f }, 0f));
 
       y2 = (int) (y
               - ((_aa.threshold.value - min) / range) * _aa.graphHeight);