Clover icon

jalviewX

  1. Project Clover database Wed Oct 31 2018 15:13:58 GMT
  2. Package jalview.schemes

File FeatureColour.java

 

Coverage histogram

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

Code metrics

150
278
34
1
946
651
119
0.43
8.18
34
3.5

Classes

Class Line # Actions
FeatureColour 52 278 119 52
0.8874458788.7%
 

Contributing tests

This file is covered by 58 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  46 toggle public static FeatureColourI parseJalviewFeatureColour(String descriptor)
176    {
177  46 StringTokenizer gcol = new StringTokenizer(descriptor, BAR, true);
178  46 float min = Float.MIN_VALUE;
179  46 float max = Float.MAX_VALUE;
180  46 boolean byLabel = false;
181  46 boolean byAttribute = false;
182  46 String attName = null;
183  46 String mincol = null;
184  46 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  46 String nextToken = gcol.nextToken();
191  46 if (nextToken == BAR)
192    {
193  0 throw new IllegalArgumentException(
194    "Expected either 'label' or a colour specification in the line: "
195    + descriptor);
196    }
197  46 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  44 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  43 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  39 mincol = nextToken;
220    }
221   
222    /*
223    * if only one token, it can validly be label, attributeName,
224    * or a plain colour value
225    */
226  46 if (!gcol.hasMoreTokens())
227    {
228  35 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  33 Color colour = ColorUtils.parseColourString(descriptor);
241  33 if (colour == null)
242    {
243  1 throw new IllegalArgumentException(
244    "Invalid colour descriptor: " + descriptor);
245    }
246  32 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  11 boolean autoScaled = true;
259  11 String tok = null, minval, maxval;
260  11 String noValueColour = NO_VALUE_MIN;
261   
262  11 if (mincol != null)
263    {
264    // at least four more tokens
265  11 if (mincol.equals(BAR))
266    {
267  2 mincol = null;
268    }
269    else
270    {
271  9 gcol.nextToken(); // skip next '|'
272    }
273  11 maxcol = gcol.nextToken();
274  11 if (maxcol.equals(BAR))
275    {
276  2 maxcol = null;
277    }
278    else
279    {
280  9 gcol.nextToken(); // skip next '|'
281    }
282  11 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  11 if (tok.equalsIgnoreCase(NO_VALUE_MIN))
289    {
290  0 tok = gcol.nextToken();
291  0 tok = gcol.nextToken();
292    }
293  11 else if (tok.equalsIgnoreCase(NO_VALUE_MAX))
294    {
295  0 noValueColour = NO_VALUE_MAX;
296  0 tok = gcol.nextToken();
297  0 tok = gcol.nextToken();
298    }
299  11 else if (tok.equalsIgnoreCase(NO_VALUE_NONE))
300    {
301  0 noValueColour = NO_VALUE_NONE;
302  0 tok = gcol.nextToken();
303  0 tok = gcol.nextToken();
304    }
305   
306  11 gcol.nextToken(); // skip next '|'
307  11 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  9 minval = tok;
316    }
317  11 maxval = gcol.nextToken();
318  11 if (gcol.hasMoreTokens())
319    {
320  8 gcol.nextToken(); // skip next '|'
321    }
322  11 try
323    {
324  11 if (minval.length() > 0)
325    {
326  11 min = new Float(minval).floatValue();
327    }
328    } catch (Exception e)
329    {
330  0 throw new IllegalArgumentException(
331    "Couldn't parse the minimum value for graduated colour ("
332    + descriptor + ")");
333    }
334  11 try
335    {
336  11 if (maxval.length() > 0)
337    {
338  11 max = new Float(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  11 FeatureColour featureColour;
362  11 try
363    {
364  11 Color minColour = ColorUtils.parseColourString(mincol);
365  11 Color maxColour = ColorUtils.parseColourString(maxcol);
366  11 Color noColour = noValueColour.equals(NO_VALUE_MAX) ? maxColour
367  11 : (noValueColour.equals(NO_VALUE_NONE) ? null : minColour);
368  11 featureColour = new FeatureColour(minColour, maxColour, noColour, min,
369    max);
370  11 featureColour.setColourByLabel(minColour == null);
371  11 featureColour.setAutoScaled(autoScaled);
372  11 if (byAttribute)
373    {
374  3 featureColour.setAttributeName(
375    FeatureMatcher.fromAttributeDisplayName(attName));
376    }
377    // add in any additional parameters
378  11 String ttype = null, tval = null;
379  11 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  11 if (featureColour.hasThreshold())
401    {
402  8 try
403    {
404  8 gcol.nextToken();
405  8 tval = gcol.nextToken();
406  8 featureColour.setThreshold(new Float(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  11 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  11 return featureColour;
424    } catch (Exception e)
425    {
426  0 throw new IllegalArgumentException(e.getMessage());
427    }
428    }
429   
430    /**
431    * Default constructor
432    */
 
433  19 toggle public FeatureColour()
434    {
435  19 this((Color) null);
436    }
437   
438    /**
439    * Constructor given a simple colour
440    *
441    * @param c
442    */
 
443  1347 toggle public FeatureColour(Color c)
444    {
445  1347 minColour = Color.WHITE;
446  1347 maxColour = Color.BLACK;
447  1347 noColour = DEFAULT_NO_COLOUR;
448  1347 minRed = 0f;
449  1347 minGreen = 0f;
450  1347 minBlue = 0f;
451  1347 deltaRed = 0f;
452  1347 deltaGreen = 0f;
453  1347 deltaBlue = 0f;
454  1347 colour = c;
455    }
456   
457    /**
458    * Constructor given a colour range and a score range, defaulting 'no value
459    * colour' to be the same as minimum colour
460    *
461    * @param low
462    * @param high
463    * @param min
464    * @param max
465    */
 
466  19 toggle public FeatureColour(Color low, Color high, float min, float max)
467    {
468  19 this(low, high, low, min, max);
469    }
470   
471    /**
472    * Copy constructor
473    *
474    * @param fc
475    */
 
476  11 toggle public FeatureColour(FeatureColour fc)
477    {
478  11 graduatedColour = fc.graduatedColour;
479  11 colour = fc.colour;
480  11 minColour = fc.minColour;
481  11 maxColour = fc.maxColour;
482  11 noColour = fc.noColour;
483  11 minRed = fc.minRed;
484  11 minGreen = fc.minGreen;
485  11 minBlue = fc.minBlue;
486  11 deltaRed = fc.deltaRed;
487  11 deltaGreen = fc.deltaGreen;
488  11 deltaBlue = fc.deltaBlue;
489  11 base = fc.base;
490  11 range = fc.range;
491  11 isHighToLow = fc.isHighToLow;
492  11 attributeName = fc.attributeName;
493  11 setAboveThreshold(fc.isAboveThreshold());
494  11 setBelowThreshold(fc.isBelowThreshold());
495  11 setThreshold(fc.getThreshold());
496  11 setAutoScaled(fc.isAutoScaled());
497  11 setColourByLabel(fc.isColourByLabel());
498    }
499   
500    /**
501    * Copy constructor with new min/max ranges
502    *
503    * @param fc
504    * @param min
505    * @param max
506    */
 
507  5 toggle public FeatureColour(FeatureColour fc, float min, float max)
508    {
509  5 this(fc);
510  5 updateBounds(min, max);
511    }
512   
513    /**
514    * Constructor for a graduated colour
515    *
516    * @param low
517    * @param high
518    * @param noValueColour
519    * @param min
520    * @param max
521    */
 
522  47 toggle public FeatureColour(Color low, Color high, Color noValueColour,
523    float min, float max)
524    {
525  47 if (low == null)
526    {
527  2 low = Color.white;
528    }
529  47 if (high == null)
530    {
531  2 high = Color.black;
532    }
533  47 graduatedColour = true;
534  47 colour = null;
535  47 minColour = low;
536  47 maxColour = high;
537  47 noColour = noValueColour;
538  47 threshold = Float.NaN;
539  47 isHighToLow = min >= max;
540  47 minRed = low.getRed() / 255f;
541  47 minGreen = low.getGreen() / 255f;
542  47 minBlue = low.getBlue() / 255f;
543  47 deltaRed = (high.getRed() / 255f) - minRed;
544  47 deltaGreen = (high.getGreen() / 255f) - minGreen;
545  47 deltaBlue = (high.getBlue() / 255f) - minBlue;
546  47 if (isHighToLow)
547    {
548  6 base = max;
549  6 range = min - max;
550    }
551    else
552    {
553  41 base = min;
554  41 range = max - min;
555    }
556    }
557   
 
558  33922 toggle @Override
559    public boolean isGraduatedColour()
560    {
561  33922 return graduatedColour;
562    }
563   
564    /**
565    * Sets the 'graduated colour' flag. If true, also sets 'colour by label' to
566    * false.
567    */
 
568  27 toggle void setGraduatedColour(boolean b)
569    {
570  27 graduatedColour = b;
571  27 if (b)
572    {
573  0 setColourByLabel(false);
574    }
575    }
576   
 
577  26234 toggle @Override
578    public Color getColour()
579    {
580  26234 return colour;
581    }
582   
 
583  26 toggle @Override
584    public Color getMinColour()
585    {
586  26 return minColour;
587    }
588   
 
589  30 toggle @Override
590    public Color getMaxColour()
591    {
592  30 return maxColour;
593    }
594   
 
595  11 toggle @Override
596    public Color getNoColour()
597    {
598  11 return noColour;
599    }
600   
 
601  33977 toggle @Override
602    public boolean isColourByLabel()
603    {
604  33977 return colourByLabel;
605    }
606   
607    /**
608    * Sets the 'colour by label' flag. If true, also sets 'graduated colour' to
609    * false.
610    */
 
611  47 toggle @Override
612    public void setColourByLabel(boolean b)
613    {
614  47 colourByLabel = b;
615  47 if (b)
616    {
617  27 setGraduatedColour(false);
618    }
619    }
620   
 
621  100 toggle @Override
622    public boolean isBelowThreshold()
623    {
624  100 return belowThreshold;
625    }
626   
 
627  58 toggle @Override
628    public void setBelowThreshold(boolean b)
629    {
630  58 belowThreshold = b;
631  58 if (b)
632    {
633  18 setAboveThreshold(false);
634    }
635    }
636   
 
637  136 toggle @Override
638    public boolean isAboveThreshold()
639    {
640  136 return aboveThreshold;
641    }
642   
 
643  57 toggle @Override
644    public void setAboveThreshold(boolean b)
645    {
646  57 aboveThreshold = b;
647  57 if (b)
648    {
649  29 setBelowThreshold(false);
650    }
651    }
652   
 
653  50 toggle @Override
654    public float getThreshold()
655    {
656  50 return threshold;
657    }
658   
 
659  52 toggle @Override
660    public void setThreshold(float f)
661    {
662  52 threshold = f;
663    }
664   
 
665  48 toggle @Override
666    public boolean isAutoScaled()
667    {
668  48 return autoScaled;
669    }
670   
 
671  44 toggle @Override
672    public void setAutoScaled(boolean b)
673    {
674  44 this.autoScaled = b;
675    }
676   
677    /**
678    * {@inheritDoc}
679    */
 
680  5 toggle @Override
681    public void updateBounds(float min, float max)
682    {
683  5 if (max < min)
684    {
685  1 base = max;
686  1 range = min - max;
687  1 isHighToLow = true;
688    }
689    else
690    {
691  4 base = min;
692  4 range = max - min;
693  4 isHighToLow = false;
694    }
695    }
696   
697    /**
698    * Returns the colour for the given instance of the feature. This may be a
699    * simple colour, a colour generated from the feature description (if
700    * isColourByLabel()), or a colour derived from the feature score (if
701    * isGraduatedColour()).
702    *
703    * @param feature
704    * @return
705    */
 
706  26059 toggle @Override
707    public Color getColor(SequenceFeature feature)
708    {
709  26059 if (isColourByLabel())
710    {
711  3 String label = attributeName == null ? feature.getDescription()
712    : feature.getValueAsString(attributeName);
713  3 return label == null ? noColour : ColorUtils
714    .createColourFromName(label);
715    }
716   
717  26056 if (!isGraduatedColour())
718    {
719  26011 return getColour();
720    }
721   
722    /*
723    * graduated colour case, optionally with threshold
724    * may be based on feature score on an attribute value
725    * Float.NaN, or no value, is assigned the 'no value' colour
726    */
727  45 float scr = feature.getScore();
728  45 if (attributeName != null)
729    {
730  13 try
731    {
732  13 String attVal = feature.getValueAsString(attributeName);
733  13 scr = Float.valueOf(attVal);
734    } catch (Throwable e)
735    {
736  5 scr = Float.NaN;
737    }
738    }
739  45 if (Float.isNaN(scr))
740    {
741  10 return noColour;
742    }
743   
744  35 if (isAboveThreshold() && scr <= threshold)
745    {
746  10 return null;
747    }
748   
749  25 if (isBelowThreshold() && scr >= threshold)
750    {
751  5 return null;
752    }
753  20 if (range == 0.0)
754    {
755  0 return getMaxColour();
756    }
757  20 float scl = (scr - base) / range;
758  20 if (isHighToLow)
759    {
760  0 scl = -scl;
761    }
762  20 if (scl < 0f)
763    {
764  0 scl = 0f;
765    }
766  20 if (scl > 1f)
767    {
768  0 scl = 1f;
769    }
770  20 return new Color(minRed + scl * deltaRed, minGreen + scl * deltaGreen,
771    minBlue + scl * deltaBlue);
772    }
773   
774    /**
775    * Returns the maximum score of the graduated colour range
776    *
777    * @return
778    */
 
779  36 toggle @Override
780    public float getMax()
781    {
782    // regenerate the original values passed in to the constructor
783  36 return (isHighToLow) ? base : (base + range);
784    }
785   
786    /**
787    * Returns the minimum score of the graduated colour range
788    *
789    * @return
790    */
 
791  36 toggle @Override
792    public float getMin()
793    {
794    // regenerate the original value passed in to the constructor
795  36 return (isHighToLow) ? (base + range) : base;
796    }
797   
 
798  7844 toggle @Override
799    public boolean isSimpleColour()
800    {
801  7844 return (!isColourByLabel() && !isGraduatedColour());
802    }
803   
 
804  40 toggle @Override
805    public boolean hasThreshold()
806    {
807  40 return isAboveThreshold() || isBelowThreshold();
808    }
809   
 
810  19 toggle @Override
811    public String toJalviewFormat(String featureType)
812    {
813  19 String colourString = null;
814  19 if (isSimpleColour())
815    {
816  4 colourString = Format.getHexString(getColour());
817    }
818    else
819    {
820  15 StringBuilder sb = new StringBuilder(32);
821  15 if (isColourByAttribute())
822    {
823  3 sb.append(ATTRIBUTE).append(BAR);
824  3 sb.append(
825    FeatureMatcher.toAttributeDisplayName(getAttributeName()));
826    }
827  12 else if (isColourByLabel())
828    {
829  4 sb.append(LABEL);
830    }
831    else
832    {
833  8 sb.append(SCORE);
834    }
835  15 if (isGraduatedColour())
836    {
837  9 sb.append(BAR).append(Format.getHexString(getMinColour()))
838    .append(BAR);
839  9 sb.append(Format.getHexString(getMaxColour())).append(BAR);
840  9 String noValue = minColour.equals(noColour) ? NO_VALUE_MIN
841  5 : (maxColour.equals(noColour) ? NO_VALUE_MAX
842    : NO_VALUE_NONE);
843  9 sb.append(noValue).append(BAR);
844  9 if (!isAutoScaled())
845    {
846  5 sb.append(ABSOLUTE).append(BAR);
847    }
848    }
849    else
850    {
851    /*
852    * colour by text with score threshold: empty fields for
853    * minColour and maxColour (not used)
854    */
855  6 if (hasThreshold())
856    {
857  3 sb.append(BAR).append(BAR).append(BAR);
858    }
859    }
860  15 if (hasThreshold() || isGraduatedColour())
861    {
862  12 sb.append(getMin()).append(BAR);
863  12 sb.append(getMax()).append(BAR);
864  12 if (isBelowThreshold())
865    {
866  5 sb.append(BELOW).append(BAR).append(getThreshold());
867    }
868  7 else if (isAboveThreshold())
869    {
870  3 sb.append(ABOVE).append(BAR).append(getThreshold());
871    }
872    else
873    {
874  4 sb.append("none");
875    }
876    }
877  15 colourString = sb.toString();
878    }
879  19 return String.format("%s\t%s", featureType, colourString);
880    }
881   
 
882  48 toggle @Override
883    public boolean isColourByAttribute()
884    {
885  48 return attributeName != null;
886    }
887   
 
888  54 toggle @Override
889    public String[] getAttributeName()
890    {
891  54 return attributeName;
892    }
893   
 
894  31 toggle @Override
895    public void setAttributeName(String... name)
896    {
897  31 attributeName = name;
898    }
899   
 
900  17 toggle @Override
901    public String getDescription()
902    {
903  17 if (isSimpleColour())
904    {
905  1 return "r=" + colour.getRed() + ",g=" + colour.getGreen() + ",b="
906    + colour.getBlue();
907    }
908  16 StringBuilder tt = new StringBuilder();
909  16 String by = null;
910   
911  16 if (getAttributeName() != null)
912    {
913  4 by = FeatureMatcher.toAttributeDisplayName(getAttributeName());
914    }
915  12 else if (isColourByLabel())
916    {
917  5 by = I18N_LABEL;
918    }
919    else
920    {
921  7 by = I18N_SCORE;
922    }
923  16 tt.append(MessageManager.formatMessage("action.by_title_param", by));
924   
925    /*
926    * add threshold if any
927    */
928  16 if (isAboveThreshold() || isBelowThreshold())
929    {
930  10 tt.append(" (");
931  10 if (isColourByLabel())
932    {
933    /*
934    * Jalview features file supports the combination of
935    * colour by label or attribute text with score threshold
936    */
937  3 tt.append(I18N_SCORE).append(" ");
938    }
939  10 tt.append(isAboveThreshold() ? "> " : "< ");
940  10 tt.append(getThreshold()).append(")");
941    }
942   
943  16 return tt.toString();
944    }
945   
946    }