Clover icon

Coverage Report

  1. Project Clover database Mon Nov 11 2024 17:27:16 GMT
  2. Package jalview.schemes

File FeatureColour.java

 

Coverage histogram

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

Code metrics

160
282
33
1
975
669
124
0.44
8.55
33
3.76

Classes

Class Line # Actions
FeatureColour 55 282 124
0.9010526590.1%
 

Contributing tests

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