Clover icon

Coverage Report

  1. Project Clover database Thu Aug 13 2020 12:04:21 BST
  2. Package jalview.schemes

File FeatureColour.java

 

Coverage histogram

../../img/srcFileCovDistChart9.png
12% of files have more coverage

Code metrics

160
282
33
1
971
667
124
0.44
8.55
33
3.76

Classes

Class Line # Actions
FeatureColour 52 282 124
0.9010526590.1%
 

Contributing tests

This file is covered by 68 tests. .

Source view

1    /*
2    * Jalview - A Sequence Alignment Editor and Viewer ($$Version-Rel$$)
3    * Copyright (C) $$Year-Rel$$ The Jalview Authors
4    *
5    * This file is part of Jalview.
6    *
7    * Jalview is free software: you can redistribute it and/or
8    * modify it under the terms of the GNU General Public License
9    * as published by the Free Software Foundation, either version 3
10    * of the License, or (at your option) any later version.
11    *
12    * Jalview is distributed in the hope that it will be useful, but
13    * WITHOUT ANY WARRANTY; without even the implied warranty
14    * of MERCHANTABILITY or FITNESS FOR A PARTICULAR
15    * PURPOSE. See the GNU General Public License for more details.
16    *
17    * You should have received a copy of the GNU General Public License
18    * along with Jalview. If not, see <http://www.gnu.org/licenses/>.
19    * The Jalview Authors are detailed in the 'AUTHORS' file.
20    */
21    package jalview.schemes;
22   
23    import jalview.api.FeatureColourI;
24    import jalview.datamodel.SequenceFeature;
25    import jalview.datamodel.features.FeatureMatcher;
26    import jalview.util.ColorUtils;
27    import jalview.util.Format;
28    import jalview.util.MessageManager;
29   
30    import java.awt.Color;
31    import java.util.StringTokenizer;
32   
33    /**
34    * A class that represents a colour scheme for a feature type. Options supported
35    * are currently
36    * <ul>
37    * <li>a simple colour e.g. Red</li>
38    * <li>colour by label - a colour is generated from the feature description</li>
39    * <li>graduated colour by feature score</li>
40    * <ul>
41    * <li>minimum and maximum score range must be provided</li>
42    * <li>minimum and maximum value colours should be specified</li>
43    * <li>a colour for 'no value' may optionally be provided</li>
44    * <li>colours for intermediate scores are interpolated RGB values</li>
45    * <li>there is an optional threshold above/below which to colour values</li>
46    * <li>the range may be the full value range, or may be limited by the threshold
47    * value</li>
48    * </ul>
49    * <li>colour by (text) value of a named attribute</li> <li>graduated colour by
50    * (numeric) value of a named attribute</li> </ul>
51    */
 
52    public class FeatureColour implements FeatureColourI
53    {
54    private static final String I18N_LABEL = MessageManager
55    .getString("label.label");
56   
57    private static final String I18N_SCORE = MessageManager
58    .getString("label.score");
59   
60    private static final String ABSOLUTE = "abso";
61   
62    private static final String ABOVE = "above";
63   
64    private static final String BELOW = "below";
65   
66    /*
67    * constants used to read or write a Jalview Features file
68    */
69    private static final String LABEL = "label";
70   
71    private static final String SCORE = "score";
72   
73    private static final String ATTRIBUTE = "attribute";
74   
75    private static final String NO_VALUE_MIN = "noValueMin";
76   
77    private static final String NO_VALUE_MAX = "noValueMax";
78   
79    private static final String NO_VALUE_NONE = "noValueNone";
80   
81    static final Color DEFAULT_NO_COLOUR = null;
82   
83    private static final String BAR = "|";
84   
85    final private Color colour;
86   
87    final private Color minColour;
88   
89    final private Color maxColour;
90   
91    /*
92    * colour to use for colour by attribute when the
93    * attribute value is absent
94    */
95    final private Color noColour;
96   
97    /*
98    * if true, then colour has a gradient based on a numerical
99    * range (either feature score, or an attribute value)
100    */
101    private boolean graduatedColour;
102   
103    /*
104    * if true, colour values are generated from a text string,
105    * either feature description, or an attribute value
106    */
107    private boolean colourByLabel;
108   
109    /*
110    * if not null, the value of [attribute, [sub-attribute] ...]
111    * is used for colourByLabel or graduatedColour
112    */
113    private String[] attributeName;
114   
115    private float threshold;
116   
117    private float base;
118   
119    private float range;
120   
121    private boolean belowThreshold;
122   
123    private boolean aboveThreshold;
124   
125    private boolean isHighToLow;
126   
127    private boolean autoScaled;
128   
129    final private float minRed;
130   
131    final private float minGreen;
132   
133    final private float minBlue;
134   
135    final private float deltaRed;
136   
137    final private float deltaGreen;
138   
139    final private float deltaBlue;
140   
141    /**
142    * Parses a Jalview features file format colour descriptor
143    * <p>
144    * <code>
145    * [label|score|[attribute|attributeName]|][mincolour|maxcolour|
146    * [absolute|]minvalue|maxvalue|[noValueOption|]thresholdtype|thresholdvalue]</code>
147    * <p>
148    * 'Score' is optional (default) for a graduated colour. An attribute with
149    * sub-attribute should be written as (for example) CSQ:Consequence.
150    * noValueOption is one of <code>noValueMin, noValueMax, noValueNone</code>
151    * with default noValueMin.
152    * <p>
153    * Examples:
154    * <ul>
155    * <li>red</li>
156    * <li>a28bbb</li>
157    * <li>25,125,213</li>
158    * <li>label</li>
159    * <li>attribute|CSQ:PolyPhen</li>
160    * <li>label|||0.0|0.0|above|12.5</li>
161    * <li>label|||0.0|0.0|below|12.5</li>
162    * <li>red|green|12.0|26.0|none</li>
163    * <li>score|red|green|12.0|26.0|none</li>
164    * <li>attribute|AF|red|green|12.0|26.0|none</li>
165    * <li>attribute|AF|red|green|noValueNone|12.0|26.0|none</li>
166    * <li>a28bbb|3eb555|12.0|26.0|above|12.5</li>
167    * <li>a28bbb|3eb555|abso|12.0|26.0|below|12.5</li>
168    * </ul>
169    *
170    * @param descriptor
171    * @return
172    * @throws IllegalArgumentException
173    * if not parseable
174    */
 
175  60 toggle public static FeatureColourI parseJalviewFeatureColour(String descriptor)
176    {
177  60 StringTokenizer gcol = new StringTokenizer(descriptor, BAR, true);
178  60 float min = Float.MIN_VALUE;
179  60 float max = Float.MAX_VALUE;
180  60 boolean byLabel = false;
181  60 boolean byAttribute = false;
182  60 String attName = null;
183  60 String mincol = null;
184  60 String maxcol = null;
185   
186    /*
187    * first token should be 'label', or 'score', or an
188    * attribute name, or simple colour, or minimum colour
189    */
190  60 String nextToken = gcol.nextToken();
191  60 if (nextToken == BAR)
192    {
193  0 throw new IllegalArgumentException(
194    "Expected either 'label' or a colour specification in the line: "
195    + descriptor);
196    }
197  60 if (nextToken.toLowerCase().startsWith(LABEL))
198    {
199  2 byLabel = true;
200    // get the token after the next delimiter:
201  2 mincol = (gcol.hasMoreTokens() ? gcol.nextToken() : null);
202  2 mincol = (gcol.hasMoreTokens() ? gcol.nextToken() : null);
203    }
204  58 else if (nextToken.toLowerCase().startsWith(SCORE))
205    {
206  1 mincol = (gcol.hasMoreTokens() ? gcol.nextToken() : null);
207  1 mincol = (gcol.hasMoreTokens() ? gcol.nextToken() : null);
208    }
209  57 else if (nextToken.toLowerCase().startsWith(ATTRIBUTE))
210    {
211  4 byAttribute = true;
212  4 attName = (gcol.hasMoreTokens() ? gcol.nextToken() : null);
213  4 attName = (gcol.hasMoreTokens() ? gcol.nextToken() : null);
214  4 mincol = (gcol.hasMoreTokens() ? gcol.nextToken() : null);
215  4 mincol = (gcol.hasMoreTokens() ? gcol.nextToken() : null);
216    }
217    else
218    {
219  53 mincol = nextToken;
220    }
221   
222    /*
223    * if only one token, it can validly be label, attributeName,
224    * or a plain colour value
225    */
226  60 if (!gcol.hasMoreTokens())
227    {
228  45 if (byLabel || byAttribute)
229    {
230  2 FeatureColourI fc = new FeatureColour();
231  2 fc.setColourByLabel(true);
232  2 if (byAttribute)
233    {
234  1 fc.setAttributeName(
235    FeatureMatcher.fromAttributeDisplayName(attName));
236    }
237  2 return fc;
238    }
239   
240  43 Color colour = ColorUtils.parseColourString(descriptor);
241  43 if (colour == null)
242    {
243  1 throw new IllegalArgumentException(
244    "Invalid colour descriptor: " + descriptor);
245    }
246  42 return new FeatureColour(colour);
247    }
248   
249    /*
250    * continue parsing for min/max/no colour (if graduated)
251    * and for threshold (colour by text or graduated)
252    */
253   
254    /*
255    * autoScaled == true: colours range over actual score range
256    * autoScaled == false ('abso'): colours range over min/max range
257    */
258  15 boolean autoScaled = true;
259  15 String tok = null, minval, maxval;
260  15 String noValueColour = NO_VALUE_MIN;
261   
262  15 if (mincol != null)
263    {
264    // at least four more tokens
265  15 if (mincol.equals(BAR))
266    {
267  2 mincol = null;
268    }
269    else
270    {
271  13 gcol.nextToken(); // skip next '|'
272    }
273  15 maxcol = gcol.nextToken();
274  15 if (maxcol.equals(BAR))
275    {
276  2 maxcol = null;
277    }
278    else
279    {
280  13 gcol.nextToken(); // skip next '|'
281    }
282  15 tok = gcol.nextToken();
283   
284    /*
285    * check for specifier for colour for no attribute value
286    * (new in 2.11, defaults to minColour if not specified)
287    */
288  15 if (tok.equalsIgnoreCase(NO_VALUE_MIN))
289    {
290  1 tok = gcol.nextToken();
291  1 tok = gcol.nextToken();
292    }
293  14 else if (tok.equalsIgnoreCase(NO_VALUE_MAX))
294    {
295  1 noValueColour = NO_VALUE_MAX;
296  1 tok = gcol.nextToken();
297  1 tok = gcol.nextToken();
298    }
299  13 else if (tok.equalsIgnoreCase(NO_VALUE_NONE))
300    {
301  1 noValueColour = NO_VALUE_NONE;
302  1 tok = gcol.nextToken();
303  1 tok = gcol.nextToken();
304    }
305   
306  15 gcol.nextToken(); // skip next '|'
307  15 if (tok.toLowerCase().startsWith(ABSOLUTE))
308    {
309  2 minval = gcol.nextToken();
310  2 gcol.nextToken(); // skip next '|'
311  2 autoScaled = false;
312    }
313    else
314    {
315  13 minval = tok;
316    }
317  15 maxval = gcol.nextToken();
318  15 if (gcol.hasMoreTokens())
319    {
320  9 gcol.nextToken(); // skip next '|'
321    }
322  15 try
323    {
324  15 if (minval.length() > 0)
325    {
326  15 min = Float.valueOf(minval).floatValue();
327    }
328    } catch (Exception e)
329    {
330  1 throw new IllegalArgumentException(
331    "Couldn't parse the minimum value for graduated colour ('"
332    + minval + "')");
333    }
334  14 try
335    {
336  14 if (maxval.length() > 0)
337    {
338  14 max = Float.valueOf(maxval).floatValue();
339    }
340    } catch (Exception e)
341    {
342  0 throw new IllegalArgumentException(
343    "Couldn't parse the maximum value for graduated colour ("
344    + descriptor + ")");
345    }
346    }
347    else
348    {
349    /*
350    * dummy min/max colours for colour by text
351    * (label or attribute value)
352    */
353  0 mincol = "white";
354  0 maxcol = "black";
355  0 byLabel = true;
356    }
357   
358    /*
359    * construct the FeatureColour!
360    */
361  14 FeatureColour featureColour;
362  14 try
363    {
364  14 Color minColour = ColorUtils.parseColourString(mincol);
365  14 Color maxColour = ColorUtils.parseColourString(maxcol);
366  14 Color noColour = noValueColour.equals(NO_VALUE_MAX) ? maxColour
367  13 : (noValueColour.equals(NO_VALUE_NONE) ? null : minColour);
368  14 featureColour = new FeatureColour(maxColour, minColour, maxColour,
369    noColour, min, max);
370  14 featureColour.setColourByLabel(minColour == null);
371  14 featureColour.setAutoScaled(autoScaled);
372  14 if (byAttribute)
373    {
374  3 featureColour.setAttributeName(
375    FeatureMatcher.fromAttributeDisplayName(attName));
376    }
377    // add in any additional parameters
378  14 String ttype = null, tval = null;
379  14 if (gcol.hasMoreTokens())
380    {
381    // threshold type and possibly a threshold value
382  8 ttype = gcol.nextToken();
383  8 if (ttype.toLowerCase().startsWith(BELOW))
384    {
385  4 featureColour.setBelowThreshold(true);
386    }
387  4 else if (ttype.toLowerCase().startsWith(ABOVE))
388    {
389  4 featureColour.setAboveThreshold(true);
390    }
391    else
392    {
393  0 if (!ttype.toLowerCase().startsWith("no"))
394    {
395  0 System.err.println(
396    "Ignoring unrecognised threshold type : " + ttype);
397    }
398    }
399    }
400  14 if (featureColour.hasThreshold())
401    {
402  8 try
403    {
404  8 gcol.nextToken();
405  8 tval = gcol.nextToken();
406  8 featureColour.setThreshold(Float.valueOf(tval).floatValue());
407    } catch (Exception e)
408    {
409  0 System.err.println("Couldn't parse threshold value as a float: ("
410    + tval + ")");
411    }
412    }
413  14 if (gcol.hasMoreTokens())
414    {
415  0 System.err.println(
416    "Ignoring additional tokens in parameters in graduated colour specification\n");
417  0 while (gcol.hasMoreTokens())
418    {
419  0 System.err.println(BAR + gcol.nextToken());
420    }
421  0 System.err.println("\n");
422    }
423  14 return featureColour;
424    } catch (Exception e)
425    {
426  0 throw new IllegalArgumentException(e.getMessage());
427    }
428    }
429   
430    /**
431    * Default constructor
432    */
 
433  36 toggle public FeatureColour()
434    {
435  36 this((Color) null);
436    }
437   
438    /**
439    * Constructor given a simple colour. This also 'primes' a graduated colour
440    * range, where the maximum colour is the given simple colour, and the minimum
441    * colour a paler shade of it. This is for convenience when switching from a
442    * simple colour to a graduated colour scheme.
443    *
444    * @param c
445    */
 
446  986 toggle public FeatureColour(Color c)
447    {
448    /*
449    * set max colour to the simple colour, min colour to a paler shade of it
450    */
451  986 this(c, c == null ? Color.white : ColorUtils.bleachColour(c, 0.9f),
452  986 c == null ? Color.black : c, DEFAULT_NO_COLOUR, 0, 0);
453   
454    /*
455    * but enforce simple colour for now!
456    */
457  986 setGraduatedColour(false);
458    }
459   
460    /**
461    * Copy constructor
462    *
463    * @param fc
464    */
 
465  10 toggle public FeatureColour(FeatureColour fc)
466    {
467  10 graduatedColour = fc.graduatedColour;
468  10 colour = fc.colour;
469  10 minColour = fc.minColour;
470  10 maxColour = fc.maxColour;
471  10 noColour = fc.noColour;
472  10 minRed = fc.minRed;
473  10 minGreen = fc.minGreen;
474  10 minBlue = fc.minBlue;
475  10 deltaRed = fc.deltaRed;
476  10 deltaGreen = fc.deltaGreen;
477  10 deltaBlue = fc.deltaBlue;
478  10 base = fc.base;
479  10 range = fc.range;
480  10 isHighToLow = fc.isHighToLow;
481  10 attributeName = fc.attributeName;
482  10 setAboveThreshold(fc.isAboveThreshold());
483  10 setBelowThreshold(fc.isBelowThreshold());
484  10 setThreshold(fc.getThreshold());
485  10 setAutoScaled(fc.isAutoScaled());
486  10 setColourByLabel(fc.isColourByLabel());
487    }
488   
489    /**
490    * Constructor that sets both simple and graduated colour values. This allows
491    * alternative colour schemes to be 'preserved' while switching between them
492    * to explore their effects on the visualisation.
493    * <p>
494    * This sets the colour scheme to 'graduated' by default. Override this if
495    * wanted by calling <code>setGraduatedColour(false)</code> for a simple
496    * colour, or <code>setColourByLabel(true)</code> for colour by label.
497    *
498    * @param myColour
499    * @param low
500    * @param high
501    * @param noValueColour
502    * @param min
503    * @param max
504    */
 
505  1039 toggle public FeatureColour(Color myColour, Color low, Color high,
506    Color noValueColour, float min, float max)
507    {
508  1039 if (low == null)
509    {
510  2 low = Color.white;
511    }
512  1039 if (high == null)
513    {
514  2 high = Color.black;
515    }
516  1039 colour = myColour;
517  1039 minColour = low;
518  1039 maxColour = high;
519  1039 setGraduatedColour(true);
520  1039 noColour = noValueColour;
521  1039 threshold = Float.NaN;
522  1039 isHighToLow = min >= max;
523  1039 minRed = low.getRed() / 255f;
524  1039 minGreen = low.getGreen() / 255f;
525  1039 minBlue = low.getBlue() / 255f;
526  1039 deltaRed = (high.getRed() / 255f) - minRed;
527  1039 deltaGreen = (high.getGreen() / 255f) - minGreen;
528  1039 deltaBlue = (high.getBlue() / 255f) - minBlue;
529  1039 if (isHighToLow)
530    {
531  993 base = max;
532  993 range = min - max;
533    }
534    else
535    {
536  46 base = min;
537  46 range = max - min;
538    }
539    }
540   
 
541  23365 toggle @Override
542    public boolean isGraduatedColour()
543    {
544  23365 return graduatedColour;
545    }
546   
547    /**
548    * Sets the 'graduated colour' flag. If true, also sets 'colour by label' to
549    * false.
550    */
 
551  2054 toggle public void setGraduatedColour(boolean b)
552    {
553  2054 graduatedColour = b;
554  2054 if (b)
555    {
556  1039 setColourByLabel(false);
557    }
558    }
559   
 
560  20793 toggle @Override
561    public Color getColour()
562    {
563  20793 return colour;
564    }
565   
 
566  44 toggle @Override
567    public Color getMinColour()
568    {
569  44 return minColour;
570    }
571   
 
572  52 toggle @Override
573    public Color getMaxColour()
574    {
575  52 return maxColour;
576    }
577   
 
578  15 toggle @Override
579    public Color getNoColour()
580    {
581  15 return noColour;
582    }
583   
 
584  23418 toggle @Override
585    public boolean isColourByLabel()
586    {
587  23418 return colourByLabel;
588    }
589   
590    /**
591    * Sets the 'colour by label' flag. If true, also sets 'graduated colour' to
592    * false.
593    */
 
594  1088 toggle @Override
595    public void setColourByLabel(boolean b)
596    {
597  1088 colourByLabel = b;
598  1088 if (b)
599    {
600  29 setGraduatedColour(false);
601    }
602    }
603   
 
604  148 toggle @Override
605    public boolean isBelowThreshold()
606    {
607  148 return belowThreshold;
608    }
609   
 
610  64 toggle @Override
611    public void setBelowThreshold(boolean b)
612    {
613  64 belowThreshold = b;
614  64 if (b)
615    {
616  20 setAboveThreshold(false);
617    }
618    }
619   
 
620  187 toggle @Override
621    public boolean isAboveThreshold()
622    {
623  187 return aboveThreshold;
624    }
625   
 
626  65 toggle @Override
627    public void setAboveThreshold(boolean b)
628    {
629  65 aboveThreshold = b;
630  65 if (b)
631    {
632  34 setBelowThreshold(false);
633    }
634    }
635   
 
636  52 toggle @Override
637    public float getThreshold()
638    {
639  52 return threshold;
640    }
641   
 
642  58 toggle @Override
643    public void setThreshold(float f)
644    {
645  58 threshold = f;
646    }
647   
 
648  62 toggle @Override
649    public boolean isAutoScaled()
650    {
651  62 return autoScaled;
652    }
653   
 
654  48 toggle @Override
655    public void setAutoScaled(boolean b)
656    {
657  48 this.autoScaled = b;
658    }
659   
660    /**
661    * {@inheritDoc}
662    */
 
663  0 toggle @Override
664    public void updateBounds(float min, float max)
665    {
666  0 if (max < min)
667    {
668  0 base = max;
669  0 range = min - max;
670  0 isHighToLow = true;
671    }
672    else
673    {
674  0 base = min;
675  0 range = max - min;
676  0 isHighToLow = false;
677    }
678    }
679   
680    /**
681    * Returns the colour for the given instance of the feature. This may be a
682    * simple colour, a colour generated from the feature description or other
683    * attribute (if isColourByLabel()), or a colour derived from the feature
684    * score or other attribute (if isGraduatedColour()).
685    * <p>
686    * Answers null if feature score (or attribute) value lies outside a
687    * configured threshold.
688    *
689    * @param feature
690    * @return
691    */
 
692  20925 toggle @Override
693    public Color getColor(SequenceFeature feature)
694    {
695  20925 if (isColourByLabel())
696    {
697  23 String label = attributeName == null ? feature.getDescription()
698    : feature.getValueAsString(attributeName);
699  23 return label == null ? noColour : ColorUtils
700    .createColourFromName(label);
701    }
702   
703  20902 if (!isGraduatedColour())
704    {
705  20857 return getColour();
706    }
707   
708    /*
709    * graduated colour case, optionally with threshold
710    * may be based on feature score on an attribute value
711    * Float.NaN, or no value, is assigned the 'no value' colour
712    */
713  45 float scr = feature.getScore();
714  45 if (attributeName != null)
715    {
716  13 try
717    {
718  13 String attVal = feature.getValueAsString(attributeName);
719  13 scr = Float.valueOf(attVal);
720    } catch (Throwable e)
721    {
722  5 scr = Float.NaN;
723    }
724    }
725  45 if (Float.isNaN(scr))
726    {
727  10 return noColour;
728    }
729   
730  35 if (isAboveThreshold() && scr <= threshold)
731    {
732  10 return null;
733    }
734   
735  25 if (isBelowThreshold() && scr >= threshold)
736    {
737  5 return null;
738    }
739  20 if (range == 0.0)
740    {
741  0 return getMaxColour();
742    }
743  20 float scl = (scr - base) / range;
744  20 if (isHighToLow)
745    {
746  0 scl = -scl;
747    }
748  20 if (scl < 0f)
749    {
750  0 scl = 0f;
751    }
752  20 if (scl > 1f)
753    {
754  0 scl = 1f;
755    }
756  20 return new Color(minRed + scl * deltaRed, minGreen + scl * deltaGreen,
757    minBlue + scl * deltaBlue);
758    }
759   
760    /**
761    * Returns the maximum score of the graduated colour range
762    *
763    * @return
764    */
 
765  37 toggle @Override
766    public float getMax()
767    {
768    // regenerate the original values passed in to the constructor
769  37 return (isHighToLow) ? base : (base + range);
770    }
771   
772    /**
773    * Returns the minimum score of the graduated colour range
774    *
775    * @return
776    */
 
777  37 toggle @Override
778    public float getMin()
779    {
780    // regenerate the original value passed in to the constructor
781  37 return (isHighToLow) ? (base + range) : base;
782    }
783   
 
784  2388 toggle @Override
785    public boolean isSimpleColour()
786    {
787  2388 return (!isColourByLabel() && !isGraduatedColour());
788    }
789   
 
790  48 toggle @Override
791    public boolean hasThreshold()
792    {
793  48 return isAboveThreshold() || isBelowThreshold();
794    }
795   
 
796  29 toggle @Override
797    public String toJalviewFormat(String featureType)
798    {
799  29 String colourString = null;
800  29 if (isSimpleColour())
801    {
802  9 colourString = Format.getHexString(getColour());
803    }
804    else
805    {
806  20 StringBuilder sb = new StringBuilder(32);
807  20 if (isColourByAttribute())
808    {
809  3 sb.append(ATTRIBUTE).append(BAR);
810  3 sb.append(
811    FeatureMatcher.toAttributeDisplayName(getAttributeName()));
812    }
813  17 else if (isColourByLabel())
814    {
815  4 sb.append(LABEL);
816    }
817    else
818    {
819  13 sb.append(SCORE);
820    }
821  20 if (isGraduatedColour())
822    {
823  14 sb.append(BAR).append(Format.getHexString(getMinColour()))
824    .append(BAR);
825  14 sb.append(Format.getHexString(getMaxColour())).append(BAR);
826   
827    /*
828    * 'no value' colour should be null, min or max colour;
829    * if none of these, coerce to minColour
830    */
831  14 String noValue = NO_VALUE_MIN;
832  14 if (maxColour.equals(noColour))
833    {
834  5 noValue = NO_VALUE_MAX;
835    }
836  14 if (noColour == null)
837    {
838  0 noValue = NO_VALUE_NONE;
839    }
840  14 sb.append(noValue).append(BAR);
841  14 if (!isAutoScaled())
842    {
843  8 sb.append(ABSOLUTE).append(BAR);
844    }
845    }
846    else
847    {
848    /*
849    * colour by text with score threshold: empty fields for
850    * minColour and maxColour (not used)
851    */
852  6 if (hasThreshold())
853    {
854  3 sb.append(BAR).append(BAR).append(BAR);
855    }
856    }
857  20 if (hasThreshold() || isGraduatedColour())
858    {
859  17 sb.append(getMin()).append(BAR);
860  17 sb.append(getMax()).append(BAR);
861  17 if (isBelowThreshold())
862    {
863  7 sb.append(BELOW).append(BAR).append(getThreshold());
864    }
865  10 else if (isAboveThreshold())
866    {
867  4 sb.append(ABOVE).append(BAR).append(getThreshold());
868    }
869    else
870    {
871  6 sb.append("none");
872    }
873    }
874  20 colourString = sb.toString();
875    }
876  29 return String.format("%s\t%s", featureType, colourString);
877    }
878   
 
879  73 toggle @Override
880    public boolean isColourByAttribute()
881    {
882  73 return attributeName != null;
883    }
884   
 
885  57 toggle @Override
886    public String[] getAttributeName()
887    {
888  57 return attributeName;
889    }
890   
 
891  33 toggle @Override
892    public void setAttributeName(String... name)
893    {
894  33 attributeName = name;
895    }
896   
 
897  51 toggle @Override
898    public boolean isOutwithThreshold(SequenceFeature feature)
899    {
900  51 if (!isGraduatedColour())
901    {
902  21 return false;
903    }
904  30 float scr = feature.getScore();
905  30 if (attributeName != null)
906    {
907  9 try
908    {
909  9 String attVal = feature.getValueAsString(attributeName);
910  9 scr = Float.valueOf(attVal);
911    } catch (Throwable e)
912    {
913  5 scr = Float.NaN;
914    }
915    }
916  30 if (Float.isNaN(scr))
917    {
918  5 return false;
919    }
920   
921  25 return ((isAboveThreshold() && scr <= threshold)
922    || (isBelowThreshold() && scr >= threshold));
923    }
924   
 
925  17 toggle @Override
926    public String getDescription()
927    {
928  17 if (isSimpleColour())
929    {
930  1 return "r=" + colour.getRed() + ",g=" + colour.getGreen() + ",b="
931    + colour.getBlue();
932    }
933  16 StringBuilder tt = new StringBuilder();
934  16 String by = null;
935   
936  16 if (getAttributeName() != null)
937    {
938  4 by = FeatureMatcher.toAttributeDisplayName(getAttributeName());
939    }
940  12 else if (isColourByLabel())
941    {
942  5 by = I18N_LABEL;
943    }
944    else
945    {
946  7 by = I18N_SCORE;
947    }
948  16 tt.append(MessageManager.formatMessage("action.by_title_param", by));
949   
950    /*
951    * add threshold if any
952    */
953  16 if (isAboveThreshold() || isBelowThreshold())
954    {
955  10 tt.append(" (");
956  10 if (isColourByLabel())
957    {
958    /*
959    * Jalview features file supports the combination of
960    * colour by label or attribute text with score threshold
961    */
962  3 tt.append(I18N_SCORE).append(" ");
963    }
964  10 tt.append(isAboveThreshold() ? "> " : "< ");
965  10 tt.append(getThreshold()).append(")");
966    }
967   
968  16 return tt.toString();
969    }
970   
971    }