JAL-1609 refactored colour by sequence command, now performs ok
authorgmungoc <g.m.carstairs@dundee.ac.uk>
Fri, 5 Dec 2014 08:40:45 +0000 (08:40 +0000)
committergmungoc <g.m.carstairs@dundee.ac.uk>
Fri, 5 Dec 2014 08:40:45 +0000 (08:40 +0000)
src/jalview/api/SequenceRenderer.java
src/jalview/appletgui/SequenceRenderer.java
src/jalview/ext/rbvi/chimera/ChimeraCommands.java
src/jalview/gui/SequenceRenderer.java
src/jalview/util/ColorUtils.java
test/jalview/ext/rbvi/chimera/ChimeraCommandsTest.java [new file with mode: 0644]
test/jalview/gui/SequenceRendererTest.java [new file with mode: 0644]
test/jalview/util/ColorUtilsTest.java

index fbd5e4a..0ca1758 100644 (file)
@@ -29,4 +29,6 @@ public interface SequenceRenderer
 
   Color getResidueBoxColour(SequenceI sequenceI, int r);
 
+  Color getResidueColour(SequenceI seq, int position, FeatureRenderer fr);
+
 }
index 697c0d1..92d5beb 100755 (executable)
@@ -20,6 +20,7 @@
  */
 package jalview.appletgui;
 
+import jalview.api.FeatureRenderer;
 import jalview.datamodel.AlignmentAnnotation;
 import jalview.datamodel.SequenceGroup;
 import jalview.datamodel.SequenceI;
@@ -86,6 +87,31 @@ public class SequenceRenderer implements jalview.api.SequenceRenderer
     return resBoxColour;
   }
 
+  /**
+   * Get the residue colour at the given sequence position - as determined by
+   * the sequence group colour (if any), else the colour scheme, possibly
+   * overridden by a feature colour.
+   * 
+   * @param seq
+   * @param position
+   * @param fr
+   * @return
+   */
+  @Override
+  public Color getResidueColour(final SequenceI seq, int position,
+          FeatureRenderer fr)
+  {
+    // TODO replace 8 or so code duplications with calls to this method
+    // (refactored as needed)
+    Color col = getResidueBoxColour(seq, position);
+
+    if (fr != null)
+    {
+      col = fr.findFeatureColour(col, seq, position);
+    }
+    return col;
+  }
+
   void getBoxColour(ColourSchemeI cs, SequenceI seq, int i)
   {
     if (cs != null)
index 3c47ed1..4afc526 100644 (file)
@@ -27,16 +27,15 @@ import jalview.datamodel.SequenceI;
 import jalview.structure.StructureMapping;
 import jalview.structure.StructureMappingcommandSet;
 import jalview.structure.StructureSelectionManager;
+import jalview.util.ColorUtils;
 import jalview.util.Comparison;
 
 import java.awt.Color;
-import java.io.File;
-import java.io.FileOutputStream;
-import java.io.IOException;
 import java.util.ArrayList;
 import java.util.LinkedHashMap;
 import java.util.List;
 import java.util.Map;
+import java.util.TreeMap;
 
 /**
  * Routines for generating Chimera commands for Jalview/Chimera binding
@@ -59,31 +58,114 @@ public class ChimeraCommands
           SequenceI[][] sequence, SequenceRenderer sr, FeatureRenderer fr,
           AlignmentI alignment)
   {
-    String defAttrPath = null;
-    FileOutputStream fos = null;
-    try
-    {
-      File outFile = File.createTempFile("jalviewdefattr", ".xml");
-      outFile.deleteOnExit();
-      defAttrPath = outFile.getPath();
-      fos = new FileOutputStream(outFile);
-      fos.write("attribute: jalviewclr\n".getBytes());
-    } catch (IOException e1)
-    {
-      e1.printStackTrace();
-    }
-    List<StructureMappingcommandSet> cset = new ArrayList<StructureMappingcommandSet>();
+    Map<Color, Map<Integer, Map<String, List<int[]>>>> colourMap = buildColoursMap(
+            ssm, files, sequence, sr, fr, alignment);
 
+    List<String> colourCommands = buildColourCommands(colourMap);
+
+    StructureMappingcommandSet cs = new StructureMappingcommandSet(
+            ChimeraCommands.class, null,
+            colourCommands.toArray(new String[0]));
+
+    return new StructureMappingcommandSet[]
+    { cs };
+  }
+
+  /**
+   * Traverse the map of colours/models/chains/positions to construct a list of
+   * 'color' commands (one per distinct colour used). The format of each command
+   * is
+   * 
+   * <blockquote> color colorname #modelnumber:range.chain e.g. color #00ff00
+   * #0:2.B,4.B,9-12.B|#1:1.A,2-6.A,...
+   * 
+   * @see http
+   *      ://www.cgl.ucsf.edu/chimera/current/docs/UsersGuide/midas/frameatom_spec
+   *      .html </pre>
+   * 
+   * @param colourMap
+   * @return
+   */
+  protected static List<String> buildColourCommands(
+          Map<Color, Map<Integer, Map<String, List<int[]>>>> colourMap)
+  {
     /*
-     * Map of { colour, positionSpecs}
+     * This version concatenates all commands into a single String (semi-colon
+     * delimited). If length limit issues arise, refactor to return one color
+     * command per colour.
      */
-    Map<String, StringBuilder> colranges = new LinkedHashMap<String, StringBuilder>();
-    StringBuilder setAttributes = new StringBuilder(256);
-    String lastColour = "none";
-    Color lastCol = null;
+    List<String> commands = new ArrayList<String>();
+    StringBuilder sb = new StringBuilder(256);
+    boolean firstColour = true;
+    for (Color colour : colourMap.keySet())
+    {
+      String colourCode = ColorUtils.toTkCode(colour);
+      if (!firstColour)
+      {
+        sb.append("; ");
+      }
+      sb.append("color ").append(colourCode).append(" ");
+      firstColour = false;
+      boolean firstModelForColour = true;
+      final Map<Integer, Map<String, List<int[]>>> colourData = colourMap.get(colour);
+      for (Integer model : colourData.keySet())
+      {
+        boolean firstPositionForModel = true;
+        if (!firstModelForColour)
+        {
+          sb.append("|");
+        }
+        firstModelForColour = false;
+        sb.append("#").append(model).append(":");
+
+        final Map<String, List<int[]>> modelData = colourData.get(model);
+        for (String chain : modelData.keySet())
+        {
+          for (int[] range : modelData.get(chain))
+          {
+            if (!firstPositionForModel)
+            {
+              sb.append(",");
+            }
+            if (range[0] == range[1])
+            {
+              sb.append(range[0]);
+            }
+            else
+            {
+              sb.append(range[0]).append("-").append(range[1]);
+            }
+            sb.append(".").append(chain);
+            firstPositionForModel = false;
+          }
+        }
+      }
+    }
+    commands.add(sb.toString());
+    return commands;
+  }
+
+  /**
+   * <pre>
+   * Build a data structure which maps contiguous subsequences for each colour. 
+   * This generates a data structure from which we can easily generate the 
+   * Chimera command for colour by sequence.
+   * Color
+   *     Model number
+   *         Chain
+   *             list of start/end ranges
+   * Ordering is by order of addition (for colours and positions), natural ordering (for models and chains)
+   * </pre>
+   */
+  protected static Map<Color, Map<Integer, Map<String, List<int[]>>>> buildColoursMap(
+          StructureSelectionManager ssm, String[] files,
+          SequenceI[][] sequence, SequenceRenderer sr, FeatureRenderer fr,
+          AlignmentI alignment)
+  {
+    Map<Color, Map<Integer, Map<String, List<int[]>>>> colourMap = new LinkedHashMap<Color, Map<Integer, Map<String, List<int[]>>>>();
+    Color lastColour = null;
     for (int pdbfnum = 0; pdbfnum < files.length; pdbfnum++)
     {
-      boolean startModel = true;
       StructureMapping[] mapping = ssm.getMapping(files[pdbfnum]);
 
       if (mapping == null || mapping.length < 1)
@@ -116,200 +198,93 @@ public class ChimeraCommands
                 continue;
               }
 
-              Color col = getResidueColour(seq, r, sr, fr);
+              Color colour = sr.getResidueColour(seq, r, fr);
+              final String chain = mapping[m].getChain();
+
               /*
                * Just keep incrementing the end position for this colour range
                * _unless_ colour, PDB model or chain has changed, or there is a
                * gap in the mapped residue sequence
                */
-              final boolean newColour = !col.equals(lastCol);
+              final boolean newColour = !colour.equals(lastColour);
               final boolean nonContig = lastPos + 1 != pos;
-              final boolean newChain = !mapping[m].getChain().equals(lastChain);
-              if (newColour || nonContig || startModel || newChain)
+              final boolean newChain = !chain.equals(lastChain);
+              if (newColour || nonContig || newChain)
               {
-                if (/* lastCol != null */startPos != -1)
+                if (startPos != -1)
                 {
-                  addColourRange(colranges, lastCol, pdbfnum, startPos,
-                          lastPos, lastChain, startModel);
-                  startModel = false;
+                  addColourRange(colourMap, lastColour, pdbfnum, startPos,
+                          lastPos, lastChain);
                 }
-                // lastCol = null;
                 startPos = pos;
               }
-              lastCol = col;
+              lastColour = colour;
               lastPos = pos;
-              // lastModel = pdbfnum;
-              lastChain = mapping[m].getChain();
+              lastChain = chain;
             }
             // final colour range
-            if (lastCol != null)
+            if (lastColour != null)
             {
-              addColourRange(colranges, lastCol, pdbfnum, startPos,
-                      lastPos, lastChain, false);
+              addColourRange(colourMap, lastColour, pdbfnum, startPos,
+                      lastPos, lastChain);
             }
             break;
           }
         }
       }
     }
-      try
-      {
-      lastColour = buildColourCommands(cset, colranges,
-                fos, setAttributes);
-      } catch (IOException e)
-      {
-        e.printStackTrace();
-      }
-
-    try
-    {
-      fos.close();
-    } catch (IOException e)
-    {
-      e.printStackTrace();
-    }
-
-    /*
-     * Send a rangeColor command, preceded by either defattr or setattr,
-     * whichever we end up preferring!
-     * 
-     * rangecolor requires a minimum of two attribute values to operate on
-     */
-    StringBuilder rangeColor = new StringBuilder(256);
-    rangeColor.append("rangecolor jalviewclr");
-    int colourId = 0;
-    for (String colour : colranges.keySet())
-    {
-      colourId++;
-      rangeColor.append(" " + colourId + " " + colour);
-    }
-    String rangeColorCommand = rangeColor.toString();
-    if (rangeColorCommand.split(" ").length < 5)
-    {
-      rangeColorCommand += " max " + lastColour;
-    }
-    final String defAttrCommand = "defattr " + defAttrPath
-            + " raiseTool false";
-    final String setAttrCommand = setAttributes.toString();
-    final String attrCommand = false ? defAttrCommand : setAttrCommand;
-    cset.add(new StructureMappingcommandSet(ChimeraCommands.class, null,
-            new String[]
-            { attrCommand /* , rangeColorCommand */}));
-
-    return cset.toArray(new StructureMappingcommandSet[cset.size()]);
+    return colourMap;
   }
 
   /**
-   * Get the residue colour at the given sequence position - as determined by
-   * the sequence group colour (if any), else the colour scheme, possibly
-   * overridden by a feature colour.
+   * Helper method to add one contiguous colour range to the colour map.
    * 
-   * @param seq
-   * @param position
-   * @param sr
-   * @param fr
-   * @return
-   */
-  protected static Color getResidueColour(final SequenceI seq,
-          int position, SequenceRenderer sr, FeatureRenderer fr)
-  {
-    Color col = sr.getResidueBoxColour(seq, position);
-
-    if (fr != null)
-    {
-      col = fr.findFeatureColour(col, seq, position);
-    }
-    return col;
-  }
-
-  /**
-   * Helper method to build the colour commands for one PDBfile.
-   * 
-   * @param cset
-   *          the list of commands to be added to
-   * @param colranges
-   *          the map of colours to residue positions already determined
-   * @param fos
-   *          file to write 'defattr' commands to
-   * @param setAttributes
-   * @throws IOException
-   */
-  protected static String buildColourCommands(
-          List<StructureMappingcommandSet> cset,
-          Map<String, StringBuilder> colranges,
-          FileOutputStream fos, StringBuilder setAttributes)
-          throws IOException
-  {
-    int colourId = 0;
-    String lastColour = null;
-    for (String colour : colranges.keySet())
-    {
-      lastColour = colour;
-      colourId++;
-      /*
-       * Using color command directly is slow for larger structures.
-       * setAttributes.append("color #" + colour + " " + colranges.get(colour)+
-       * ";");
-       */
-      setAttributes.append("color " + colour + " " + colranges.get(colour)
-              + ";");
-      final String atomSpec = new String(colranges.get(colour));
-      // setAttributes.append("setattr r jalviewclr " + colourId + " "
-      // + atomSpec + ";");
-      fos.write(("\t" + atomSpec + "\t" + colourId + "\n").getBytes());
-    }
-    return lastColour;
-  }
-
-  /**
-   * Helper method to record a range of positions of the same colour.
-   * 
-   * @param colranges
+   * @param colourMap
    * @param colour
    * @param model
    * @param startPos
    * @param endPos
    * @param chain
-   * @param changeModel
    */
-  private static void addColourRange(Map<String, StringBuilder> colranges,
-          Color colour, int model, int startPos, int endPos, String chain,
-          boolean startModel)
+  protected static void addColourRange(
+          Map<Color, Map<Integer, Map<String, List<int[]>>>> colourMap,
+          Color colour, int model, int startPos, int endPos, String chain)
   {
-    String colstring = "#" + ((colour.getRed() < 16) ? "0" : "")
-            + Integer.toHexString(colour.getRed())
-            + ((colour.getGreen()< 16) ? "0":"")+Integer.toHexString(colour.getGreen())
-            + ((colour.getBlue()< 16) ? "0":"")+Integer.toHexString(colour.getBlue());
-    StringBuilder currange = colranges.get(colstring);
-    if (currange == null)
-    {
-      colranges.put(colstring, currange = new StringBuilder(256));
-    }
     /*
-     * Format as (e.g.) #0:1-3.A,5.A,7-10.A,...#1:1-4.B,..etc
+     * Get/initialize map of data for the colour
      */
-    // if (currange.length() > 0)
-    // {
-    // currange.append("|");
-    // }
-    // currange.append("#" + model + ":" + ((startPos==endPos) ? startPos :
-    // startPos + "-"
-    // + endPos) + "." + chain);
-    if (currange.length() == 0)
+    Map<Integer, Map<String, List<int[]>>> colourData = colourMap
+            .get(colour);
+    if (colourData == null)
     {
-      currange.append("#" + model + ":");
+      colourMap
+              .put(colour,
+                      colourData = new TreeMap<Integer, Map<String, List<int[]>>>());
     }
-    else if (startModel)
+
+    /*
+     * Get/initialize map of data for the colour and model
+     */
+    Map<String, List<int[]>> modelData = colourData.get(model);
+    if (modelData == null)
     {
-      currange.append(",#" + model + ":");
+      colourData.put(model, modelData = new TreeMap<String, List<int[]>>());
     }
-    else
+
+    /*
+     * Get/initialize map of data for colour, model and chain
+     */
+    List<int[]> chainData = modelData.get(chain);
+    if (chainData == null)
     {
-      currange.append(",");
+      modelData.put(chain, chainData = new ArrayList<int[]>());
     }
-    final String rangeSpec = (startPos == endPos) ? Integer
-            .toString(startPos) : (startPos + "-" + endPos);
-    currange.append(rangeSpec + "." + chain);
+
+    /*
+     * Add the start/end positions
+     */
+    chainData.add(new int[]
+    { startPos, endPos });
   }
 
 }
index bcbebbd..177fc83 100755 (executable)
@@ -20,6 +20,7 @@
  */
 package jalview.gui;
 
+import jalview.api.FeatureRenderer;
 import jalview.datamodel.AlignmentAnnotation;
 import jalview.datamodel.SequenceGroup;
 import jalview.datamodel.SequenceI;
@@ -80,11 +81,12 @@ public class SequenceRenderer implements jalview.api.SequenceRenderer
     // If EPS graphics, stringWidth will be a double, not an int
     double dwidth = fm.getStringBounds("M", g).getWidth();
 
-    monospacedFont = (dwidth == fm.getStringBounds("|", g).getWidth() && (float) av.charWidth == dwidth);
+    monospacedFont = (dwidth == fm.getStringBounds("|", g).getWidth() && av.charWidth == dwidth);
 
     this.renderGaps = renderGaps;
   }
 
+  @Override
   public Color getResidueBoxColour(SequenceI seq, int i)
   {
     allGroups = av.getAlignment().findAllGroups(seq);
@@ -105,6 +107,31 @@ public class SequenceRenderer implements jalview.api.SequenceRenderer
   }
 
   /**
+   * Get the residue colour at the given sequence position - as determined by
+   * the sequence group colour (if any), else the colour scheme, possibly
+   * overridden by a feature colour.
+   * 
+   * @param seq
+   * @param position
+   * @param fr
+   * @return
+   */
+  @Override
+  public Color getResidueColour(final SequenceI seq, int position,
+          FeatureRenderer fr)
+  {
+    // TODO replace 8 or so code duplications with calls to this method
+    // (refactored as needed)
+    Color col = getResidueBoxColour(seq, position);
+
+    if (fr != null)
+    {
+      col = fr.findFeatureColour(col, seq, position);
+    }
+    return col;
+  }
+
+  /**
    * DOCUMENT ME!
    * 
    * @param cs
@@ -188,7 +215,9 @@ public class SequenceRenderer implements jalview.api.SequenceRenderer
           int y1)
   {
     if (seq == null)
+     {
       return; // fix for racecondition
+    }
     int i = start;
     int length = seq.getLength();
 
index fd76086..44ce010 100644 (file)
@@ -60,6 +60,26 @@ public class ColorUtils
   }
 
   /**
+   * Convert to Tk colour code format
+   * 
+   * @param colour
+   * @return
+   * @see http
+   *      ://www.cgl.ucsf.edu/chimera/current/docs/UsersGuide/colortool.html#
+   *      tkcode
+   */
+  public static final String toTkCode(Color colour)
+  {
+    String colstring = "#" + ((colour.getRed() < 16) ? "0" : "")
+            + Integer.toHexString(colour.getRed())
+            + ((colour.getGreen() < 16) ? "0" : "")
+            + Integer.toHexString(colour.getGreen())
+            + ((colour.getBlue() < 16) ? "0" : "")
+            + Integer.toHexString(colour.getBlue());
+    return colstring;
+  }
+
+  /**
    * Returns a colour three shades darker. Note you can't guarantee that
    * brighterThan reverses this, as darkerThan may result in black.
    * 
diff --git a/test/jalview/ext/rbvi/chimera/ChimeraCommandsTest.java b/test/jalview/ext/rbvi/chimera/ChimeraCommandsTest.java
new file mode 100644 (file)
index 0000000..7dfbba1
--- /dev/null
@@ -0,0 +1,92 @@
+package jalview.ext.rbvi.chimera;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+
+import java.awt.Color;
+import java.util.Arrays;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+
+import org.junit.Test;
+
+public class ChimeraCommandsTest
+{
+  @Test
+  public void testAddColourRange()
+  {
+    Map<Color, Map<Integer, Map<String, List<int[]>>>> map = new LinkedHashMap<Color, Map<Integer, Map<String, List<int[]>>>>();
+    ChimeraCommands.addColourRange(map, Color.pink, 1, 2, 4, "A");
+    ChimeraCommands.addColourRange(map, Color.pink, 1, 8, 8, "A");
+    ChimeraCommands.addColourRange(map, Color.pink, 1, 5, 7, "B");
+    ChimeraCommands.addColourRange(map, Color.red, 1, 3, 5, "A");
+    ChimeraCommands.addColourRange(map, Color.red, 0, 1, 4, "B");
+    ChimeraCommands.addColourRange(map, Color.orange, 0, 5, 9, "C");
+
+    // three colours mapped
+    assertEquals(3, map.keySet().size());
+
+    // Red has two models, Pink and Orange one each
+    assertEquals(2, map.get(Color.red).keySet().size());
+    assertEquals(1, map.get(Color.orange).keySet().size());
+    assertEquals(1, map.get(Color.pink).keySet().size());
+
+    // pink model 1 has two chains, red.0 / red.1 / orange.0 one each
+    assertEquals(2, map.get(Color.pink).get(1).keySet().size());
+    assertEquals(1, map.get(Color.red).get(0).keySet().size());
+    assertEquals(1, map.get(Color.red).get(1).keySet().size());
+    assertEquals(1, map.get(Color.orange).get(0).keySet().size());
+
+    // inspect positions
+    List<int[]> posList = map.get(Color.pink).get(1).get("A");
+    assertEquals(2, posList.size());
+    assertTrue(Arrays.equals(new int[]
+      { 2, 4 }, posList.get(0)));
+    assertTrue(Arrays.equals(new int[]
+      { 8, 8 }, posList.get(1)));
+
+    posList = map.get(Color.pink).get(1).get("B");
+    assertEquals(1, posList.size());
+    assertTrue(Arrays.equals(new int[]
+      { 5, 7 }, posList.get(0)));
+
+    posList = map.get(Color.red).get(0).get("B");
+    assertEquals(1, posList.size());
+    assertTrue(Arrays.equals(new int[]
+      { 1, 4 }, posList.get(0)));
+
+    posList = map.get(Color.red).get(1).get("A");
+    assertEquals(1, posList.size());
+    assertTrue(Arrays.equals(new int[]
+      { 3, 5 }, posList.get(0)));
+
+    posList = map.get(Color.orange).get(0).get("C");
+    assertEquals(1, posList.size());
+    assertTrue(Arrays.equals(new int[]
+      { 5, 9 }, posList.get(0)));
+  }
+
+  @Test
+  public void testBuildColourCommands()
+  {
+
+    Map<Color, Map<Integer, Map<String, List<int[]>>>> map = new LinkedHashMap<Color, Map<Integer, Map<String, List<int[]>>>>();
+    ChimeraCommands.addColourRange(map, Color.blue, 0, 2, 5, "A");
+    ChimeraCommands.addColourRange(map, Color.blue, 0, 7, 7, "B");
+    ChimeraCommands.addColourRange(map, Color.blue, 0, 9, 23, "A");
+    ChimeraCommands.addColourRange(map, Color.blue, 1, 1, 1, "A");
+    ChimeraCommands.addColourRange(map, Color.blue, 1, 4, 7, "B");
+    ChimeraCommands.addColourRange(map, Color.yellow, 1, 8, 8, "A");
+    ChimeraCommands.addColourRange(map, Color.yellow, 1, 3, 5, "A");
+    ChimeraCommands.addColourRange(map, Color.red, 0, 3, 5, "A");
+
+    // Colours should appear in the Chimera command in the order in which
+    // they were added; within colour, by model, by chain, and positions as
+    // added
+    String command = ChimeraCommands.buildColourCommands(map).get(0);
+    assertEquals(
+            "color #0000ff #0:2-5.A,9-23.A,7.B|#1:1.A,4-7.B; color #ffff00 #1:8.A,3-5.A; color #ff0000 #0:3-5.A",
+            command);
+  }
+}
diff --git a/test/jalview/gui/SequenceRendererTest.java b/test/jalview/gui/SequenceRendererTest.java
new file mode 100644 (file)
index 0000000..3f8b96a
--- /dev/null
@@ -0,0 +1,36 @@
+package jalview.gui;
+
+import static org.junit.Assert.assertEquals;
+import jalview.datamodel.Alignment;
+import jalview.datamodel.AlignmentI;
+import jalview.datamodel.Sequence;
+import jalview.datamodel.SequenceI;
+import jalview.schemes.ZappoColourScheme;
+
+import java.awt.Color;
+
+import org.junit.Test;
+
+public class SequenceRendererTest
+{
+
+  @Test
+  public void testGetResidueBoxColour_zappo()
+  {
+    SequenceI seq = new Sequence("name", "MATVLGSPRAPAFF"); // FER1_MAIZE...
+    AlignmentI al = new Alignment(new SequenceI[]
+      { seq });
+    final AlignViewport av = new AlignViewport(al);
+    SequenceRenderer sr = new SequenceRenderer(av);
+    av.setGlobalColourScheme(new ZappoColourScheme());
+
+    // @see ResidueProperties.zappo
+    assertEquals(Color.pink, sr.getResidueColour(seq, 0, null)); // M
+    assertEquals(Color.green, sr.getResidueColour(seq, 2, null)); // T
+    assertEquals(Color.magenta, sr.getResidueColour(seq, 5, null)); // G
+    assertEquals(Color.orange, sr.getResidueColour(seq, 12, null)); // F
+  }
+  // TODO more tests for getResidueBoxColour covering groups, feature rendering,
+  // gaps, overview...
+
+}
index da2e6ca..3bbcf27 100644 (file)
@@ -39,4 +39,17 @@ public class ColorUtilsTest
             ColorUtils.brighterThan(darkColour));
     assertNull(ColorUtils.brighterThan(null));
   }
+
+  /**
+   * @see http://www.rtapo.com/notes/named_colors.html
+   */
+  @Test
+  public void testToTkCode()
+  {
+    assertEquals("#fffafa", ColorUtils.toTkCode(new Color(255, 250, 250))); // snow
+    assertEquals("#e6e6fa", ColorUtils.toTkCode(new Color(230, 230, 250))); // lavender
+    assertEquals("#dda0dd", ColorUtils.toTkCode(new Color(221, 160, 221))); // plum
+    assertEquals("#800080", ColorUtils.toTkCode(new Color(128, 0, 128))); // purple
+    assertEquals("#00ff00", ColorUtils.toTkCode(new Color(0, 255, 0))); // lime
+  }
 }