JAL-2965 graduated sequence point colour from back to front
authorgmungoc <g.m.carstairs@dundee.ac.uk>
Mon, 30 Apr 2018 09:43:17 +0000 (10:43 +0100)
committergmungoc <g.m.carstairs@dundee.ac.uk>
Mon, 30 Apr 2018 09:43:17 +0000 (10:43 +0100)
src/jalview/gui/RotatableCanvas.java
src/jalview/util/ColorUtils.java
test/jalview/util/ColorUtilsTest.java

index 6fabd95..615b403 100755 (executable)
@@ -27,6 +27,7 @@ import jalview.datamodel.SequenceI;
 import jalview.datamodel.SequencePoint;
 import jalview.math.RotatableMatrix;
 import jalview.math.RotatableMatrix.Axis;
+import jalview.util.ColorUtils;
 import jalview.util.MessageManager;
 import jalview.viewmodel.AlignmentViewport;
 
@@ -160,9 +161,14 @@ public class RotatableCanvas extends JPanel implements MouseListener,
 
   }
 
-  public void showLabels(boolean b)
+  /**
+   * Refreshes the display with labels shown (or not)
+   * 
+   * @param show
+   */
+  public void showLabels(boolean show)
   {
-    showLabels = b;
+    showLabels = show;
     repaint();
   }
 
@@ -204,7 +210,7 @@ public class RotatableCanvas extends JPanel implements MouseListener,
    * Resets axes to the initial state: x-axis to the right, y-axis up, z-axis to
    * back (so obscured in a 2-D display)
    */
-  public void resetAxes()
+  protected void resetAxes()
   {
     axisEndPoints[0] = new Point(1f, 0f, 0f);
     axisEndPoints[1] = new Point(0f, 1f, 0f);
@@ -216,7 +222,7 @@ public class RotatableCanvas extends JPanel implements MouseListener,
    * sequence point, and also the min-max range (width) for each dimension, and
    * the maximum width for all dimensions
    */
-  public void findWidth()
+  protected void findWidth()
   {
     max = new float[DIMS];
     min = new float[DIMS];
@@ -251,7 +257,7 @@ public class RotatableCanvas extends JPanel implements MouseListener,
    * 
    * @return DOCUMENT ME!
    */
-  public float findScale()
+  protected float findScale()
   {
     int dim;
     int w;
@@ -283,7 +289,7 @@ public class RotatableCanvas extends JPanel implements MouseListener,
   /**
    * Computes and saves the position of the centre of the view
    */
-  public void findCentre()
+  protected void findCentre()
   {
     findWidth();
 
@@ -434,26 +440,8 @@ public class RotatableCanvas extends JPanel implements MouseListener,
        * gray if sequence is currently selected
        */
       SequencePoint sp = points.elementAt(i);
-      SequenceI sequence = sp.getSequence();
-      Color sequenceColour = av.getSequenceColour(sequence);
-      g.setColor(
-              sequenceColour == Color.black ? Color.white : sequenceColour);
-      if (av.getSelectionGroup() != null)
-      {
-        if (av.getSelectionGroup().getSequences(null)
-                .contains(sequence))
-        {
-          g.setColor(Color.gray);
-        }
-      }
-
-      /*
-       * dim sequence points 'at the back'
-       */
-      if (sp.coord.z < centre.z)
-      {
-        g.setColor(g.getColor().darker());
-      }
+      Color sequenceColour = getSequencePointColour(sp);
+      g.setColor(sequenceColour);
 
       int halfwidth = getWidth() / 2;
       int halfheight = getHeight() / 2;
@@ -464,7 +452,7 @@ public class RotatableCanvas extends JPanel implements MouseListener,
       if (showLabels)
       {
         g.setColor(Color.red);
-        g.drawString(sequence.getName(), x - 3, y - 4);
+        g.drawString(sp.getSequence().getName(), x - 3, y - 4);
       }
     }
 
@@ -476,6 +464,40 @@ public class RotatableCanvas extends JPanel implements MouseListener,
     // }
   }
 
+  /**
+   * Determines the colour to use when drawing a sequence point. The colour is
+   * taken from the sequence id, with black converted to white, and then
+   * graduated from darker (at the back) to brighter (at the front) based on the
+   * z-axis coordinate of the point.
+   * 
+   * @param sp
+   * @return
+   */
+  protected Color getSequencePointColour(SequencePoint sp)
+  {
+    SequenceI sequence = sp.getSequence();
+    Color sequenceColour = av.getSequenceColour(sequence);
+    if (sequenceColour == Color.black)
+    {
+      sequenceColour = Color.white;
+    }
+    if (av.getSelectionGroup() != null)
+    {
+      if (av.getSelectionGroup().getSequences(null).contains(sequence))
+      {
+        sequenceColour = Color.gray;
+      }
+    }
+
+    /*
+     * graduate from front (brighter) to back (darker)
+     */
+    sequenceColour = ColorUtils.getGraduatedColour(sp.coord.z, min[2],
+            max[2], sequenceColour);
+
+    return sequenceColour;
+  }
+
   @Override
   public void keyTyped(KeyEvent evt)
   {
@@ -691,7 +713,7 @@ public class RotatableCanvas extends JPanel implements MouseListener,
    * @param x2
    * @param y2
    */
-  public void rectSelect(int x1, int y1, int x2, int y2)
+  protected void rectSelect(int x1, int y1, int x2, int y2)
   {
     for (int i = 0; i < npoint; i++)
     {
@@ -725,7 +747,7 @@ public class RotatableCanvas extends JPanel implements MouseListener,
    * 
    * @return
    */
-  public SequenceI findSequenceAtPoint(int x, int y)
+  protected SequenceI findSequenceAtPoint(int x, int y)
   {
     int halfwidth = getWidth() / 2;
     int halfheight = getHeight() / 2;
index 60129fb..16ff259 100644 (file)
@@ -31,6 +31,9 @@ import java.util.Random;
 
 public class ColorUtils
 {
+  // constant borrowed from java.awt.Color
+  private static final float FACTOR = 0.7f;
+
   private static final int MAX_CACHE_SIZE = 1729;
   /*
    * a cache for colours generated from text strings
@@ -372,4 +375,63 @@ public class ColorUtils
 
     return col;
   }
+
+  /**
+   * Generates a colour that is interpolated between
+   * <code>colour.darker()</code> and <code>colour.brighter()</code> in
+   * proportion as <code>value</code> is between <code>min</code> and
+   * <code>max</code>. Note that the 'neutral point' (unchanged colour) is
+   * closer to 'brighter' than to 'darker'as this is a geometric range.
+   * 
+   * @param value
+   * @param min
+   * @param max
+   * @param colour
+   * @return
+   */
+  public static Color getGraduatedColour(float value, float min, float max,
+          Color colour)
+  {
+    /*
+     * this computes the equivalent of
+     * getGraduatedColour(value, min, colour.darker(), max, colour.brighter())
+     * but avoiding object creation except for the return value
+     */
+    if (value < min)
+    {
+      value = min;
+    }
+    if (value > max)
+    {
+      value = max;
+    }
+
+    int r = colour.getRed();
+    int g = colour.getGreen();
+    int b = colour.getBlue();
+
+    /*
+     * rgb for colour.darker():
+     */
+    float minR = r * FACTOR;
+    float minG = g * FACTOR;
+    float minB = b * FACTOR;
+
+    /*
+     * rgb for colour.brighter():
+     */
+    float maxR = Math.min(255f, r / FACTOR);
+    float maxG = Math.min(255f, g / FACTOR);
+    float maxB = Math.min(255f, b / FACTOR);
+
+    /*
+     * interpolation
+     */
+    float p = (value - min) / (max - min);
+    int newR = (int) (minR + p * (maxR - minR));
+    int newG = (int) (minG + p * (maxG - minG));
+    int newB = (int) (minB + p * (maxB - minB));
+
+    return new Color(newR, newG, newB, colour.getAlpha());
+  }
 }
index fa4091f..0acd806 100644 (file)
@@ -243,4 +243,48 @@ public class ColorUtilsTest
     assertEquals(new Color(184, 184, 184),
             ColorUtils.createColourFromName("HELLO HELLO HELLO "));
   }
+
+  /**
+   * Tests for the method that returns a colour graduated between darker() and
+   * brighter()
+   */
+  @Test(groups = { "Functional" })
+  public void testGetGraduatedColour_darkerToBrighter()
+  {
+    final Color colour = new Color(180, 200, 220);
+  
+    /*
+     * value half-way between min and max does _not_ mean colour unchanged
+     * darker (*.7) is (126, 140, 154)
+     * brighter (*1/.7) is (255, 255, 255)
+     * midway is (190, 197, 204)
+     */
+    Color col = ColorUtils.getGraduatedColour(20f, 10f, 30f, colour);
+    assertEquals(190, col.getRed());
+    assertEquals(197, col.getGreen());
+    assertEquals(204, col.getBlue());
+
+    // minValue (or less) returns colour.darker()
+    // - or would do if Color.darker calculated better!
+    col = ColorUtils.getGraduatedColour(10f, 10f, 30f, colour);
+    assertEquals(col, new Color(126, 140, 154));
+    // Color.darker computes 125.999999 and rounds down!
+    assertEquals(new Color(125, 140, 154), colour.darker());
+    col = ColorUtils.getGraduatedColour(-10f, 10f, 30f, colour);
+    assertEquals(new Color(126, 140, 154), col);
+
+    // maxValue (or more) returns colour.brighter()
+    col = ColorUtils.getGraduatedColour(30f, 10f, 30f, colour);
+    assertEquals(colour.brighter(), col);
+    col = ColorUtils.getGraduatedColour(40f, 10f, 30f, colour);
+    assertEquals(colour.brighter(), col);
+
+    /*
+     * 'linear' mid-point between 0.7 and 1/0.7 is 1.057
+     * so the '
+     */
+    Color c = new Color(200, 200, 200);
+    col = ColorUtils.getGraduatedColour(106f, 0f, 200f, c);
+    assertEquals(c, col);
+  }
 }