java -Djava.ext.dirs=JALVIEW_HOME/lib -cp JALVIEW_HOME/jalview.jar jalview.bin.Jalview
-Replace JALVIEW_HOME with the full path to Jalview Installation Directory.
+Replace JALVIEW_HOME with the full path to Jalview Installation Directory. If building from source:
+
+java -Djava.ext.dirs=JALVIEW_BUILD/dist -cp JALVIEW_BUILD/dist/jalview.jar jalview.bin.Jalview
+
##################
<li><!-- JAL-2636 -->Scale mark not shown when close to right hand end of alignment</li>
<li><!-- JAL-2684 -->Pairwise alignment only aligns selected regions of each selected sequence</li>
<li><!-- JAL-2973 -->Alignment ruler height set incorrectly after canceling the Alignment Window's Font dialog</li>
- <li><!-- JAL-2036 -->Show cross-references not enabled after restoring project until a new view is created</li>
+ <li><!-- JAL-2036 -->Show cross-references not enabled after restoring project until a new view is created</li>
+ <li><!-- JAL-2756 -->Warning popup about use of SEQUENCE_ID in URL links appears when only default EMBL-EBI link is configured (since 2.10.2b2)</li>
</ul>
<strong><em>Applet</em></strong><br/>
<ul>
label.view_pdb_structure = View PDB Structure
label.min = Min:
label.max = Max:
-label.colour_by_label = Colour by label
label.new_feature = New Feature
label.match_case = Match Case
label.view_alignment_editor = View in alignment editor
label.threshold_feature_below_threshold = Below Threshold
label.adjust_threshold = Adjust threshold
label.toggle_absolute_relative_display_threshold = Toggle between absolute and relative display threshold.
-label.display_features_same_type_different_label_using_different_colour = Display features of the same type with a different label using a different colour. (e.g. domain features)
+label.colour_by_label_tip = Display features of the same type with a different label using a different colour. (e.g. domain features)
label.select_colour_minimum_value = Select Colour for Minimum Value
label.select_colour_maximum_value = Select Colour for Maximum Value
label.open_url_param = Open URL {0}
label.matchCondition_gt = >
label.matchCondition_ge = >=
label.numeric_required = The value should be numeric
-label.no_attributes_known = No attributes known
+label.no_attributes = No attributes known
+label.no_numeric_attributes = No numeric attributes known
label.filters = Filters
label.match_condition = Match condition
label.join_conditions = Join conditions with
label.feature_to_filter = Feature to filter
+label.colour_by_value = Colour by value
+label.colour_by_text = Colour by text
+label.score = Score
+label.attribute = Attribute
+label.colour_by_label = Colour by label
+label.variable_colour = Variable colour
+label.no_colour = No colour:
+label.select_no_value_colour = Select colour when no value
+label.select_new_colour = Select new colour
Color getMaxColour();
/**
+ * Returns the 'no value' colour (used when a feature lacks score, or the
+ * attribute, being used for colouring)
+ *
+ * @return
+ */
+ Color getNoColour();
+
+ /**
* Answers true if the feature has a single colour, i.e. if isColourByLabel()
* and isGraduatedColour() both answer false
*
Color getColor(SequenceFeature feature);
/**
- * Update the min-max range for a graduated colour scheme
+ * Update the min-max range for a graduated colour scheme. Note that the
+ * colour scheme may be configured to colour by feature score, or a
+ * (numeric-valued) attribute - the caller should ensure that the correct
+ * range is being set.
*
* @param min
* @param max
* @return
*/
String toJalviewFormat(String featureType);
+
+ /**
+ * Answers true if colour is by attribute text or numerical value
+ *
+ * @return
+ */
+ boolean isColourByAttribute();
+
+ /**
+ * Answers the name of the attribute used for colouring if any, or null
+ *
+ * @return
+ */
+ String getAttributeName();
+
+ /**
+ * Sets the name of the attribute used for colouring if any, or null to remove
+ * this property
+ *
+ * @return
+ */
+ void setAttributeName(String name);
}
.formatMessage("label.annotation_for_displayid", new Object[]
{ seq.getDisplayId(true) }));
new SequenceAnnotationReport(null).createSequenceAnnotationReport(
- contents, seq, true, true,
- (ap.seqPanel.seqCanvas.fr != null)
- ? ap.seqPanel.seqCanvas.fr.getMinMax()
- : null);
+ contents, seq, true, true, ap.seqPanel.seqCanvas.fr);
contents.append("</p>");
}
Frame frame = new Frame();
}
otherDetails.put(key, value);
- FeatureAttributes.getInstance().addAttribute(this.type, key);
+ recordAttribute(key, value);
}
}
+ /**
+ * Notifies the addition of a feature attribute. This lets us keep track of
+ * which attributes are present on each feature type, and also the range of
+ * numerical-valued attributes.
+ *
+ * @param key
+ * @param value
+ */
+ protected void recordAttribute(String key, Object value)
+ {
+ String attDesc = null;
+ if (source != null)
+ {
+ attDesc = FeatureSources.getInstance().getSource(source)
+ .getAttributeName(key);
+ }
+
+ FeatureAttributes.getInstance().addAttribute(this.type, key, attDesc,
+ value.toString());
+ }
+
/*
* The following methods are added to maintain the castor Uniprot mapping file
* for the moment.
import java.util.HashMap;
import java.util.List;
import java.util.Map;
-import java.util.Set;
-import java.util.TreeSet;
+import java.util.TreeMap;
/**
* A singleton class to hold the set of attributes known for each feature type
{
private static FeatureAttributes instance = new FeatureAttributes();
- private Map<String, Set<String>> attributes;
+ private Map<String, Map<String, AttributeData>> attributes;
+
+ private class AttributeData
+ {
+ /*
+ * description(s) for this attribute, if known
+ * (different feature source might have differing descriptions)
+ */
+ List<String> description;
+
+ /*
+ * minimum value (of any numeric values recorded)
+ */
+ float min = 0f;
+
+ /*
+ * maximum value (of any numeric values recorded)
+ */
+ float max = 0f;
+
+ /*
+ * flag is set true if any numeric value is detected for this attribute
+ */
+ boolean hasValue = false;
+
+ /**
+ * Note one instance of this attribute, recording unique, non-null names,
+ * and the min/max of any numerical values
+ *
+ * @param desc
+ * @param value
+ */
+ void addInstance(String desc, String value)
+ {
+ addDescription(desc);
+
+ if (value != null)
+ {
+ try
+ {
+ float f = Float.valueOf(value);
+ min = Float.min(min, f);
+ max = Float.max(max, f);
+ hasValue = true;
+ } catch (NumberFormatException e)
+ {
+ // ok, wasn't a number, ignore for min-max purposes
+ }
+ }
+ }
+
+ /**
+ * Answers the description of the attribute, if recorded and unique, or null if either no, or more than description is recorded
+ * @return
+ */
+ public String getDescription()
+ {
+ if (description != null && description.size() == 1)
+ {
+ return description.get(0);
+ }
+ return null;
+ }
+
+ /**
+ * Adds the given description to the list of known descriptions (without
+ * duplication)
+ *
+ * @param desc
+ */
+ public void addDescription(String desc)
+ {
+ if (desc != null)
+ {
+ if (description == null)
+ {
+ description = new ArrayList<>();
+ }
+ if (!description.contains(desc))
+ {
+ description.add(desc);
+ }
+ }
+ }
+ }
/**
* Answers the singleton instance of this class
return Collections.<String> emptyList();
}
- return new ArrayList<>(attributes.get(featureType));
+ return new ArrayList<>(attributes.get(featureType).keySet());
}
/**
*/
public boolean hasAttributes(String featureType)
{
-
if (attributes.containsKey(featureType))
{
if (!attributes.get(featureType).isEmpty())
}
/**
- * Records the given attribute name for the given feature type
+ * Records the given attribute name and description for the given feature
+ * type, and updates the min-max for any numeric value
*
* @param featureType
* @param attName
+ * @param description
+ * @param value
*/
- public void addAttribute(String featureType, String attName)
+ public void addAttribute(String featureType, String attName,
+ String description, String value)
{
if (featureType == null || attName == null)
{
return;
}
- if (!attributes.containsKey(featureType))
+ Map<String, AttributeData> atts = attributes.get(featureType);
+ if (atts == null)
+ {
+ atts = new TreeMap<String, AttributeData>(
+ String.CASE_INSENSITIVE_ORDER);
+ attributes.put(featureType, atts);
+ }
+ AttributeData attData = atts.get(attName);
+ if (attData == null)
{
- attributes.put(featureType, new TreeSet<String>(
- String.CASE_INSENSITIVE_ORDER));
+ attData = new AttributeData();
+ atts.put(attName, attData);
}
+ attData.addInstance(description, value);
+ }
- attributes.get(featureType).add(attName);
+ /**
+ * Answers the description of the given attribute for the given feature type,
+ * if known and unique, else null
+ *
+ * @param featureType
+ * @param attName
+ * @return
+ */
+ public String getDescription(String featureType, String attName)
+ {
+ String desc = null;
+ Map<String, AttributeData> atts = attributes.get(featureType);
+ if (atts != null)
+ {
+ AttributeData attData = atts.get(attName);
+ if (attData != null)
+ {
+ desc = attData.getDescription();
+ }
+ }
+ return desc;
+ }
+
+ /**
+ * Answers the [min, max] value range of the given attribute for the given
+ * feature type, if known, else null. Attributes which only have text values
+ * would normally return null, however text values which happen to be numeric
+ * could result in a 'min-max' range.
+ *
+ * @param featureType
+ * @param attName
+ * @return
+ */
+ public float[] getMinMax(String featureType, String attName)
+ {
+ Map<String, AttributeData> atts = attributes.get(featureType);
+ if (atts != null)
+ {
+ AttributeData attData = atts.get(attName);
+ if (attData != null && attData.hasValue)
+ {
+ return new float[] { attData.min, attData.max };
+ }
+ }
+ return null;
+ }
+
+ /**
+ * Records the given attribute description for the given feature type
+ *
+ * @param featureType
+ * @param attName
+ * @param description
+ */
+ public void addDescription(String featureType, String attName,
+ String description)
+ {
+ if (featureType == null || attName == null)
+ {
+ return;
+ }
+
+ Map<String, AttributeData> atts = attributes.get(featureType);
+ if (atts == null)
+ {
+ atts = new TreeMap<String, AttributeData>(
+ String.CASE_INSENSITIVE_ORDER);
+ attributes.put(featureType, atts);
+ }
+ AttributeData attData = atts.get(attName);
+ if (attData == null)
+ {
+ attData = new AttributeData();
+ atts.put(attName, attData);
+ }
+ attData.addDescription(description);
}
}
{
String link = li.next();
if (link.contains(SEQUENCE_ID)
- && !link.equals(UrlConstants.DEFAULT_STRING))
+ && !UrlConstants.isDefaultString(link))
{
check = true;
int barPos = link.indexOf("|");
import jalview.api.FeatureColourI;
import jalview.datamodel.GraphLine;
+import jalview.datamodel.features.FeatureAttributes;
import jalview.schemes.FeatureColour;
import jalview.util.MessageManager;
-import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.FlowLayout;
-import java.awt.GridLayout;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.FocusAdapter;
import java.awt.event.FocusEvent;
+import java.awt.event.ItemEvent;
+import java.awt.event.ItemListener;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
+import java.util.ArrayList;
+import java.util.List;
import javax.swing.BorderFactory;
+import javax.swing.BoxLayout;
+import javax.swing.ButtonGroup;
import javax.swing.JCheckBox;
import javax.swing.JColorChooser;
import javax.swing.JComboBox;
import javax.swing.JLabel;
+import javax.swing.JMenuItem;
import javax.swing.JPanel;
+import javax.swing.JPopupMenu;
+import javax.swing.JRadioButton;
import javax.swing.JSlider;
import javax.swing.JTextField;
import javax.swing.border.LineBorder;
public class FeatureColourChooser extends JalviewDialog
{
- // FeatureSettings fs;
+ private static final int MAX_TOOLTIP_LENGTH = 50;
+
private FeatureRenderer fr;
private FeatureColourI cs;
private boolean adjusting = false;
- final private float min;
+ private float min;
- final private float max;
+ private float max;
- final private float scaleFactor;
+ private float scaleFactor;
private String type = null;
private JPanel maxColour = new JPanel();
+ private JPanel noColour = new JPanel();
+
private JComboBox<String> threshold = new JComboBox<>();
private JSlider slider = new JSlider();
private JTextField thresholdValue = new JTextField(20);
- // TODO implement GUI for tolower flag
- // JCheckBox toLower = new JCheckBox();
-
private JCheckBox thresholdIsMin = new JCheckBox();
- private JCheckBox colourByLabel = new JCheckBox();
-
private GraphLine threshline;
private Color oldmaxColour;
private Color oldminColour;
+ private Color oldNoColour;
+
private ActionListener colourEditor = null;
+ /*
+ * radio buttons to select what to colour by
+ * label, attribute text, score, attribute value
+ */
+ private JRadioButton byDescription = new JRadioButton();
+
+ private JRadioButton byAttributeText = new JRadioButton();
+
+ private JRadioButton byScore = new JRadioButton();
+
+ private JRadioButton byAttributeValue = new JRadioButton();
+
+ private ActionListener changeColourAction;
+
+ /*
+ * choice of attribute (if any) for 'colour by text'
+ */
+ private JComboBox<String> textAttributeCombo;
+
+ /*
+ * choice of attribute (if any) for 'colour by value'
+ */
+ private JComboBox<String> valueAttributeCombo;
+
/**
* Constructor
*
String title = MessageManager
.formatMessage("label.graduated_color_for_params", new String[]
{ theType });
- initDialogFrame(this, true, blocking, title, 480, 185);
+ initDialogFrame(this, true, blocking, title, 450, 300);
slider.addChangeListener(new ChangeListener()
{
}
else
{
- // promote original color to a graduated color
+ /*
+ * promote original simple color to a graduated color
+ * - by score if there is a score range, else by label
+ */
Color bl = oldcs.getColour();
if (bl == null)
{
}
// original colour becomes the maximum colour
cs = new FeatureColour(Color.white, bl, mm[0], mm[1]);
- cs.setColourByLabel(false);
+ cs.setColourByLabel(mm[0] == mm[1]);
}
minColour.setBackground(oldminColour = cs.getMinColour());
maxColour.setBackground(oldmaxColour = cs.getMaxColour());
+ noColour.setBackground(oldNoColour = cs.getNoColour());
adjusting = true;
try
jbInit();
} catch (Exception ex)
{
+ ex.printStackTrace();
+ return;
}
- // update the gui from threshold state
+
+ /*
+ * set the initial state of options on screen
+ */
thresholdIsMin.setSelected(!cs.isAutoScaled());
- colourByLabel.setSelected(cs.isColourByLabel());
+
+ if (cs.isColourByLabel())
+ {
+ if (cs.isColourByAttribute())
+ {
+ byAttributeText.setSelected(true);
+ textAttributeCombo.setEnabled(true);
+ textAttributeCombo.setSelectedItem(cs.getAttributeName());
+ }
+ else
+ {
+ byDescription.setSelected(true);
+ textAttributeCombo.setEnabled(false);
+ }
+ }
+ else
+ {
+ if (cs.isColourByAttribute())
+ {
+ byAttributeValue.setSelected(true);
+ String attributeName = cs.getAttributeName();
+ valueAttributeCombo.setSelectedItem(attributeName);
+ valueAttributeCombo.setEnabled(true);
+ setAttributeMinMax(attributeName);
+ }
+ else
+ {
+ byScore.setSelected(true);
+ valueAttributeCombo.setEnabled(false);
+ }
+ }
+
if (cs.hasThreshold())
{
// initialise threshold slider and selector
waitForInput();
}
- private void jbInit() throws Exception
+ /**
+ * Configures the initial layout
+ */
+ private void jbInit()
{
- this.setLayout(new GridLayout(4, 1));
+ this.setLayout(new BoxLayout(this, BoxLayout.Y_AXIS));
+ this.setBackground(Color.white);
+
+ changeColourAction = new ActionListener() {
+ @Override
+ public void actionPerformed(ActionEvent e)
+ {
+ changeColour(true);
+ }
+ };
+
+ /*
+ * this panel
+ * detailsPanel
+ * colourByTextPanel
+ * colourByScorePanel
+ * okCancelPanel
+ */
+ JPanel detailsPanel = new JPanel();
+ detailsPanel.setLayout(new BoxLayout(detailsPanel, BoxLayout.Y_AXIS));
- JPanel colourByPanel = initColoursPanel();
+ JPanel colourByTextPanel = initColourByTextPanel();
+ detailsPanel.add(colourByTextPanel);
- JPanel thresholdPanel = initThresholdPanel();
+ JPanel colourByValuePanel = initColourByValuePanel();
+ detailsPanel.add(colourByValuePanel);
- JPanel okCancelPanel = initOkCancelPanel();
+ /*
+ * 4 radio buttons select between colour by description, by
+ * attribute text, by score, or by attribute value
+ */
+ ButtonGroup bg = new ButtonGroup();
+ bg.add(byDescription);
+ bg.add(byAttributeText);
+ bg.add(byScore);
+ bg.add(byAttributeValue);
- this.add(colourByPanel);
- this.add(thresholdPanel);
+ JPanel okCancelPanel = initOkCancelPanel();
+ this.add(detailsPanel);
this.add(okCancelPanel);
}
/**
- * Lay out fields for threshold options
+ * Lay out fields for graduated colour by value
*
* @return
*/
- protected JPanel initThresholdPanel()
+ protected JPanel initColourByValuePanel()
{
- JPanel thresholdPanel = new JPanel();
- thresholdPanel.setLayout(new FlowLayout());
- threshold.addActionListener(new ActionListener()
+ JPanel byValuePanel = new JPanel();
+ byValuePanel.setLayout(new BoxLayout(byValuePanel, BoxLayout.Y_AXIS));
+ byValuePanel.setBorder(BorderFactory.createTitledBorder(MessageManager
+ .getString("label.colour_by_value")));
+ byValuePanel.setBackground(Color.white);
+
+ /*
+ * first row - choose colour by score or by attribute, choose attribute
+ */
+ JPanel byWhatPanel = new JPanel(new FlowLayout(FlowLayout.LEFT));
+ byWhatPanel.setBackground(Color.white);
+ byValuePanel.add(byWhatPanel);
+
+ byScore.setText(MessageManager.getString("label.score"));
+ byWhatPanel.add(byScore);
+ byScore.addActionListener(changeColourAction);
+
+ byAttributeValue.setText(MessageManager
+.getString("label.attribute"));
+ byAttributeValue.addActionListener(changeColourAction);
+ byWhatPanel.add(byAttributeValue);
+
+ List<String> attNames = FeatureAttributes.getInstance().getAttributes(
+ type);
+ valueAttributeCombo = populateAttributesDropdown(type, attNames,
+ true);
+
+ /*
+ * if no numeric atttibutes found, disable colour by attribute value
+ */
+ if (valueAttributeCombo.getItemCount() == 0)
+ {
+ byAttributeValue.setEnabled(false);
+ }
+
+ byWhatPanel.add(valueAttributeCombo);
+
+ /*
+ * second row - min/max/no colours
+ */
+ JPanel colourRangePanel = new JPanel(new FlowLayout(FlowLayout.LEFT));
+ colourRangePanel.setBackground(Color.white);
+ byValuePanel.add(colourRangePanel);
+
+ minColour.setFont(JvSwingUtils.getLabelFont());
+ minColour.setBorder(BorderFactory.createLineBorder(Color.black));
+ minColour.setPreferredSize(new Dimension(40, 20));
+ minColour.setToolTipText(MessageManager.getString("label.min_colour"));
+ minColour.addMouseListener(new MouseAdapter()
{
@Override
- public void actionPerformed(ActionEvent e)
+ public void mousePressed(MouseEvent e)
{
- changeColour(true);
+ if (minColour.isEnabled())
+ {
+ minColour_actionPerformed();
+ }
+ }
+ });
+
+ maxColour.setFont(JvSwingUtils.getLabelFont());
+ maxColour.setBorder(BorderFactory.createLineBorder(Color.black));
+ maxColour.setPreferredSize(new Dimension(40, 20));
+ maxColour.setToolTipText(MessageManager.getString("label.max_colour"));
+ maxColour.addMouseListener(new MouseAdapter()
+ {
+ @Override
+ public void mousePressed(MouseEvent e)
+ {
+ if (maxColour.isEnabled())
+ {
+ maxColour_actionPerformed();
+ }
+ }
+ });
+ maxColour.setBorder(new LineBorder(Color.black));
+
+ noColour.setFont(JvSwingUtils.getLabelFont());
+ noColour.setBorder(BorderFactory.createLineBorder(Color.black));
+ noColour.setPreferredSize(new Dimension(40, 20));
+ noColour.setToolTipText("Colour if feature has no attribute value");
+ noColour.addMouseListener(new MouseAdapter()
+ {
+ @Override
+ public void mousePressed(MouseEvent e)
+ {
+ if (e.isPopupTrigger()) // Mac: mouseReleased
+ {
+ showNoColourPopup(e);
+ return;
+ }
+ if (noColour.isEnabled())
+ {
+ noColour_actionPerformed();
+ }
+ }
+
+ @Override
+ public void mouseReleased(MouseEvent e)
+ {
+ if (e.isPopupTrigger()) // Windows: mouseReleased
+ {
+ showNoColourPopup(e);
+ e.consume();
+ return;
+ }
}
});
+ noColour.setBorder(new LineBorder(Color.black));
+
+ JLabel minText = new JLabel(MessageManager.getString("label.min"));
+ minText.setFont(JvSwingUtils.getLabelFont());
+ JLabel maxText = new JLabel(MessageManager.getString("label.max"));
+ maxText.setFont(JvSwingUtils.getLabelFont());
+ JLabel noText = new JLabel(MessageManager.getString("label.no_colour"));
+ noText.setFont(JvSwingUtils.getLabelFont());
+
+ colourRangePanel.add(minText);
+ colourRangePanel.add(minColour);
+ colourRangePanel.add(maxText);
+ colourRangePanel.add(maxColour);
+ colourRangePanel.add(noText);
+ colourRangePanel.add(noColour);
+
+ /*
+ * third row - threshold options and value
+ */
+ JPanel thresholdPanel = new JPanel(new FlowLayout(FlowLayout.LEFT));
+ thresholdPanel.setBackground(Color.white);
+ byValuePanel.add(thresholdPanel);
+
+ threshold.addActionListener(changeColourAction);
threshold.setToolTipText(MessageManager
.getString("label.threshold_feature_display_by_score"));
threshold.addItem(MessageManager
MessageManager.getString("label.adjust_threshold"));
thresholdValue.setEnabled(false);
thresholdValue.setColumns(7);
- thresholdPanel.setBackground(Color.white);
+
+ thresholdPanel.add(threshold);
+ thresholdPanel.add(slider);
+ thresholdPanel.add(thresholdValue);
+
+ /*
+ * 4th row - threshold is min / max
+ */
+ JPanel isMinMaxPanel = new JPanel(new FlowLayout(FlowLayout.LEFT));
+ isMinMaxPanel.setBackground(Color.white);
+ byValuePanel.add(isMinMaxPanel);
thresholdIsMin.setBackground(Color.white);
thresholdIsMin
.setText(MessageManager.getString("label.threshold_minmax"));
thresholdIsMin.setToolTipText(MessageManager
.getString("label.toggle_absolute_relative_display_threshold"));
- thresholdIsMin.addActionListener(new ActionListener()
+ thresholdIsMin.addActionListener(changeColourAction);
+ isMinMaxPanel.add(thresholdIsMin);
+
+ return byValuePanel;
+ }
+
+ /**
+ * Show a popup menu with options to make 'no value colour' the same as Min
+ * Colour or Max Colour
+ *
+ * @param evt
+ */
+ protected void showNoColourPopup(MouseEvent evt)
+ {
+ JPopupMenu pop = new JPopupMenu();
+
+ JMenuItem copyMin = new JMenuItem(
+ MessageManager.getString("label.min_colour"));
+ copyMin.addActionListener((new ActionListener()
+ {
+ @Override
+ public void actionPerformed(ActionEvent e)
+ {
+ noColour.setBackground(minColour.getBackground());
+ changeColour(true);
+ }
+ }));
+ pop.add(copyMin);
+
+ JMenuItem copyMax = new JMenuItem(
+ MessageManager.getString("label.max_colour"));
+ copyMax.addActionListener((new ActionListener()
{
@Override
- public void actionPerformed(ActionEvent actionEvent)
+ public void actionPerformed(ActionEvent e)
{
+ noColour.setBackground(maxColour.getBackground());
changeColour(true);
}
- });
- thresholdPanel.add(threshold);
- thresholdPanel.add(slider);
- thresholdPanel.add(thresholdValue);
- thresholdPanel.add(thresholdIsMin);
- return thresholdPanel;
+ }));
+ pop.add(copyMax);
+
+ pop.show(noColour, evt.getX(), evt.getY());
}
/**
}
/**
- * Lay out Colour by Label and min/max colour widgets
+ * Lay out Colour by Label and attribute choice elements
*
* @return
*/
- protected JPanel initColoursPanel()
+ protected JPanel initColourByTextPanel()
{
- JPanel colourByPanel = new JPanel();
- colourByPanel.setLayout(new FlowLayout());
- colourByPanel.setBackground(Color.white);
- minColour.setFont(JvSwingUtils.getLabelFont());
- minColour.setBorder(BorderFactory.createLineBorder(Color.black));
- minColour.setPreferredSize(new Dimension(40, 20));
- minColour.setToolTipText(MessageManager.getString("label.min_colour"));
- minColour.addMouseListener(new MouseAdapter()
- {
- @Override
- public void mousePressed(MouseEvent e)
- {
- if (minColour.isEnabled())
- {
- minColour_actionPerformed();
- }
- }
- });
- maxColour.setFont(JvSwingUtils.getLabelFont());
- maxColour.setBorder(BorderFactory.createLineBorder(Color.black));
- maxColour.setPreferredSize(new Dimension(40, 20));
- maxColour.setToolTipText(MessageManager.getString("label.max_colour"));
- maxColour.addMouseListener(new MouseAdapter()
- {
- @Override
- public void mousePressed(MouseEvent e)
- {
- if (maxColour.isEnabled())
- {
- maxColour_actionPerformed();
- }
- }
- });
- maxColour.setBorder(new LineBorder(Color.black));
- JLabel minText = new JLabel(MessageManager.getString("label.min"));
- minText.setFont(JvSwingUtils.getLabelFont());
- JLabel maxText = new JLabel(MessageManager.getString("label.max"));
- maxText.setFont(JvSwingUtils.getLabelFont());
+ JPanel byTextPanel = new JPanel(new FlowLayout(FlowLayout.LEFT));
+ byTextPanel.setBackground(Color.white);
+ byTextPanel.setBorder(BorderFactory.createTitledBorder(MessageManager
+ .getString("label.colour_by_text")));
+
+ byDescription.setText(MessageManager.getString("label.label"));
+ byDescription.setToolTipText(MessageManager
+ .getString("label.colour_by_label_tip"));
+ byDescription.addActionListener(changeColourAction);
+ byTextPanel.add(byDescription);
+
+ byAttributeText.setText(MessageManager.getString("label.attribute"));
+ byAttributeText.addActionListener(changeColourAction);
+ byTextPanel.add(byAttributeText);
+
+ List<String> attNames = FeatureAttributes.getInstance().getAttributes(
+ type);
+ textAttributeCombo = populateAttributesDropdown(type, attNames, false);
+ byTextPanel.add(textAttributeCombo);
- JPanel colourPanel = new JPanel();
- colourPanel.setBackground(Color.white);
- colourPanel.add(minText);
- colourPanel.add(minColour);
- colourPanel.add(maxText);
- colourPanel.add(maxColour);
- colourByPanel.add(colourByLabel, BorderLayout.WEST);
- colourByPanel.add(colourPanel, BorderLayout.EAST);
-
- colourByLabel.setBackground(Color.white);
- colourByLabel
- .setText(MessageManager.getString("label.colour_by_label"));
- colourByLabel
- .setToolTipText(MessageManager
- .getString("label.display_features_same_type_different_label_using_different_colour"));
- colourByLabel.addActionListener(new ActionListener()
+ /*
+ * disable colour by attribute if no attributes
+ */
+ if (attNames.isEmpty())
{
- @Override
- public void actionPerformed(ActionEvent actionEvent)
- {
- changeColour(true);
- }
- });
+ byAttributeText.setEnabled(false);
+ }
- return colourByPanel;
+ return byTextPanel;
}
/**
}
/**
+ * Action on clicking the 'no colour' - open a colour chooser dialog, and set
+ * the selected colour (if the user does not cancel out of the dialog)
+ */
+ protected void noColour_actionPerformed()
+ {
+ Color col = JColorChooser.showDialog(this,
+ MessageManager.getString("label.select_no_value_colour"),
+ noColour.getBackground());
+ if (col != null)
+ {
+ noColour.setBackground(col);
+ noColour.setForeground(col);
+ }
+ noColour.repaint();
+ changeColour(true);
+ }
+
+ /**
* Constructs and sets the selected colour options as the colour for the
* feature type, and repaints the alignment, and optionally the Overview
* and/or structure viewer if open
slider.setEnabled(true);
thresholdValue.setEnabled(true);
+ /*
+ * make the feature colour
+ */
FeatureColourI acg;
if (cs.isColourByLabel())
{
else
{
acg = new FeatureColour(oldminColour = minColour.getBackground(),
- oldmaxColour = maxColour.getBackground(), min, max);
+ oldmaxColour = maxColour.getBackground(),
+ oldNoColour = noColour.getBackground(), min, max);
+ }
+ String attribute = null;
+ textAttributeCombo.setEnabled(false);
+ valueAttributeCombo.setEnabled(false);
+ if (byAttributeText.isSelected())
+ {
+ attribute = (String) textAttributeCombo.getSelectedItem();
+ textAttributeCombo.setEnabled(true);
+ }
+ else if (byAttributeValue.isSelected())
+ {
+ attribute = (String) valueAttributeCombo.getSelectedItem();
+ valueAttributeCombo.setEnabled(true);
}
+ acg.setAttributeName(attribute);
if (!hasThreshold)
{
slider.setMajorTickSpacing((int) (range / 10f));
slider.setEnabled(true);
thresholdValue.setEnabled(true);
- thresholdIsMin.setEnabled(!colourByLabel.isSelected());
+ thresholdIsMin.setEnabled(!byDescription.isSelected());
adjusting = false;
}
{
acg.setAutoScaled(true);
}
- acg.setColourByLabel(colourByLabel.isSelected());
+ acg.setColourByLabel(byDescription.isSelected()
+ || byAttributeText.isSelected());
+
if (acg.isColourByLabel())
{
maxColour.setEnabled(false);
minColour.setEnabled(false);
+ noColour.setEnabled(false);
maxColour.setBackground(this.getBackground());
maxColour.setForeground(this.getBackground());
minColour.setBackground(this.getBackground());
minColour.setForeground(this.getBackground());
-
+ noColour.setBackground(this.getBackground());
+ noColour.setForeground(this.getBackground());
}
else
{
maxColour.setEnabled(true);
minColour.setEnabled(true);
+ noColour.setEnabled(true);
maxColour.setBackground(oldmaxColour);
- minColour.setBackground(oldminColour);
maxColour.setForeground(oldmaxColour);
+ minColour.setBackground(oldminColour);
minColour.setForeground(oldminColour);
+ noColour.setBackground(oldNoColour);
+ noColour.setForeground(oldNoColour);
}
+ /*
+ * save the colour, and repaint stuff
+ */
fr.setColour(type, acg);
cs = acg;
ap.paintAlignment(updateStructsAndOverview, updateStructsAndOverview);
return cs;
}
+ /**
+ * A helper method to build the drop-down choice of attributes for a feature.
+ * Where metadata is available with a description for an attribute, that is
+ * added as a tooltip. The list may be restricted to attributes for which we
+ * hold a range of numerical values (so suitable candidates for a graduated
+ * colour scheme).
+ *
+ * @param featureType
+ * @param attNames
+ * @param withNumericRange
+ */
+ protected JComboBox<String> populateAttributesDropdown(
+ String featureType, List<String> attNames,
+ boolean withNumericRange)
+ {
+ List<String> validAtts = new ArrayList<>();
+ List<String> tooltips = new ArrayList<>();
+
+ FeatureAttributes fa = FeatureAttributes.getInstance();
+ for (String attName : attNames)
+ {
+ if (withNumericRange)
+ {
+ float[] minMax = fa.getMinMax(featureType, attName);
+ if (minMax == null)
+ {
+ continue;
+ }
+ }
+ validAtts.add(attName);
+ String desc = fa.getDescription(featureType, attName);
+ if (desc != null && desc.length() > MAX_TOOLTIP_LENGTH)
+ {
+ desc = desc.substring(0, MAX_TOOLTIP_LENGTH) + "...";
+ }
+ tooltips.add(desc == null ? "" : desc);
+ }
+
+ JComboBox<String> attCombo = JvSwingUtils.buildComboWithTooltips(
+ validAtts, tooltips);
+
+ attCombo.addItemListener(new ItemListener()
+ {
+ @Override
+ public void itemStateChanged(ItemEvent e)
+ {
+ setAttributeMinMax(attCombo.getSelectedItem().toString());
+ changeColour(true);
+ }
+ });
+
+ if (validAtts.isEmpty())
+ {
+ attCombo.setToolTipText(MessageManager
+ .getString(withNumericRange ? "label.no_numeric_attributes"
+ : "label.no_attributes"));
+ }
+
+ return attCombo;
+ }
+
+ /**
+ * Updates the min-max range and scale to be that for the given attribute name
+ *
+ * @param attributeName
+ */
+ protected void setAttributeMinMax(String attributeName)
+ {
+ float[] minMax = FeatureAttributes.getInstance().getMinMax(type,
+ attributeName);
+ if (minMax != null)
+ {
+ min = minMax[0];
+ max = minMax[1];
+ scaleFactor = (max == min) ? 1f : 100f / (max - min);
+ }
+ }
+
}
import javax.help.HelpSetException;
import javax.swing.AbstractCellEditor;
import javax.swing.BorderFactory;
+import javax.swing.BoxLayout;
import javax.swing.ButtonGroup;
import javax.swing.Icon;
import javax.swing.JButton;
private static final int MIN_HEIGHT = 400;
+ private static final int MAX_TOOLTIP_LENGTH = 50;
+
DasSourceBrowser dassourceBrowser;
DasSequenceFeatureFetcher dasFeatureFetcher;
});
men.add(dens);
- if (minmax != null)
+
+ /*
+ * variable colour options include colour by label, by score,
+ * by selected attribute text, or attribute value
+ */
+ final JCheckBoxMenuItem mxcol = new JCheckBoxMenuItem(
+ MessageManager.getString("label.variable_colour"));
+ mxcol.setSelected(!featureColour.isSimpleColour());
+ men.add(mxcol);
+ mxcol.addActionListener(new ActionListener()
{
- final float[][] typeMinMax = minmax.get(type);
- /*
- * final JCheckBoxMenuItem chb = new JCheckBoxMenuItem("Vary Height"); //
- * this is broken at the moment and isn't that useful anyway!
- * chb.setSelected(minmax.get(type) != null); chb.addActionListener(new
- * ActionListener() {
- *
- * public void actionPerformed(ActionEvent e) {
- * chb.setState(chb.getState()); if (chb.getState()) { minmax.put(type,
- * null); } else { minmax.put(type, typeMinMax); } }
- *
- * });
- *
- * men.add(chb);
- */
- if (typeMinMax != null && typeMinMax[0] != null)
- {
- // if (table.getValueAt(row, column));
- // graduated colourschemes for those where minmax exists for the
- // positional features
- final JCheckBoxMenuItem mxcol = new JCheckBoxMenuItem(
- "Graduated Colour");
- mxcol.setSelected(!featureColour.isSimpleColour());
- men.add(mxcol);
- mxcol.addActionListener(new ActionListener()
- {
- JColorChooser colorChooser;
+ JColorChooser colorChooser;
- @Override
- public void actionPerformed(ActionEvent e)
+ @Override
+ public void actionPerformed(ActionEvent e)
+ {
+ if (e.getSource() == mxcol)
+ {
+ if (featureColour.isSimpleColour())
{
- if (e.getSource() == mxcol)
- {
- if (featureColour.isSimpleColour())
- {
- FeatureColourChooser fc = new FeatureColourChooser(me.fr,
- type);
- fc.addActionListener(this);
- }
- else
- {
- // bring up simple color chooser
- colorChooser = new JColorChooser();
- JDialog dialog = JColorChooser.createDialog(me,
- "Select new Colour", true, // modal
- colorChooser, this, // OK button handler
- null); // no CANCEL button handler
- colorChooser.setColor(featureColour.getMaxColour());
- dialog.setVisible(true);
- }
- }
- else
- {
- if (e.getSource() instanceof FeatureColourChooser)
- {
- FeatureColourChooser fc = (FeatureColourChooser) e
- .getSource();
- table.setValueAt(fc.getLastColour(), selectedRow, 1);
- table.validate();
- }
- else
- {
- // probably the color chooser!
- table.setValueAt(new FeatureColour(colorChooser.getColor()),
- selectedRow, 1);
- table.validate();
- me.updateFeatureRenderer(
- ((FeatureTableModel) table.getModel()).getData(),
- false);
- }
- }
+ FeatureColourChooser fc = new FeatureColourChooser(me.fr, type);
+ fc.addActionListener(this);
}
-
- });
+ else
+ {
+ // bring up simple color chooser
+ colorChooser = new JColorChooser();
+ JDialog dialog = JColorChooser.createDialog(me,
+ "Select new Colour", true, // modal
+ colorChooser, this, // OK button handler
+ null); // no CANCEL button handler
+ colorChooser.setColor(featureColour.getMaxColour());
+ dialog.setVisible(true);
+ }
+ }
+ else
+ {
+ if (e.getSource() instanceof FeatureColourChooser)
+ {
+ FeatureColourChooser fc = (FeatureColourChooser) e.getSource();
+ table.setValueAt(fc.getLastColour(), selectedRow, 1);
+ table.validate();
+ }
+ else
+ {
+ // probably the color chooser!
+ table.setValueAt(new FeatureColour(colorChooser.getColor()),
+ selectedRow, 1);
+ table.validate();
+ me.updateFeatureRenderer(
+ ((FeatureTableModel) table.getModel()).getData(), false);
+ }
+ }
}
- }
+
+ });
+
JMenuItem selCols = new JMenuItem(
MessageManager.getString("label.select_columns_containing"));
selCols.addActionListener(new ActionListener()
/*
* the panel with the filters for the selected feature type
*/
- JPanel filtersPanel = new JPanel(new GridLayout(0, 1));
+ JPanel filtersPanel = new JPanel();
+ filtersPanel.setLayout(new BoxLayout(filtersPanel, BoxLayout.Y_AXIS));
filtersPanel.setBackground(Color.white);
filtersPanel.setBorder(BorderFactory
.createTitledBorder(MessageManager.getString("label.filters")));
/*
* panel with filters - populated by refreshFiltersDisplay
*/
- chooseFiltersPanel = new JPanel(new GridLayout(0, 1));
+ chooseFiltersPanel = new JPanel();
+ chooseFiltersPanel.setLayout(new BoxLayout(chooseFiltersPanel,
+ BoxLayout.Y_AXIS));
filtersPanel.add(chooseFiltersPanel);
/*
}
if (!found)
{
- filteredFeatureChoice
+ filteredFeatureChoice // todo i18n
.addItem("No filterable feature attributes known");
}
* clear the panel and list of filter conditions
*/
chooseFiltersPanel.removeAll();
-
- String selectedType = (String) filteredFeatureChoice.getSelectedItem();
-
filters.clear();
/*
* look up attributes known for feature type
*/
+ String selectedType = (String) filteredFeatureChoice.getSelectedItem();
List<String> attNames = FeatureAttributes.getInstance().getAttributes(
selectedType);
{
orFilters.setSelected(true);
}
- Iterator<KeyedMatcherI> matchers = featureFilters.getMatchers();
- while (matchers.hasNext())
- {
- filters.add(matchers.next());
- }
+ featureFilters.getMatchers().forEach(matcher -> filters.add(matcher));
}
/*
/*
* render the conditions in rows, each in its own JPanel
*/
- int i = 0;
+ int filterIndex = 0;
for (KeyedMatcherI filter : filters)
{
String key = filter.getKey();
Condition condition = filter.getMatcher()
.getCondition();
String pattern = filter.getMatcher().getPattern();
- JPanel row = addFilter(key, attNames, condition, pattern, i);
+ JPanel row = addFilter(key, attNames, condition, pattern, filterIndex);
chooseFiltersPanel.add(row);
- i++;
+ filterIndex++;
}
filtersPane.validate();
* @param attNames
* @param cond
* @param pattern
- * @param i
+ * @param filterIndex
* @return
*/
protected JPanel addFilter(String attribute, List<String> attNames,
- Condition cond, String pattern, int i)
+ Condition cond, String pattern, int filterIndex)
{
JPanel filterRow = new JPanel(new FlowLayout(FlowLayout.LEFT));
filterRow.setBackground(Color.white);
/*
* inputs for attribute, condition, pattern
*/
- JComboBox<String> attCombo = new JComboBox<>();
+ /*
+ * drop-down choice of attribute, with description as a tooltip
+ * if we can obtain it
+ */
+ String featureType = (String) filteredFeatureChoice.getSelectedItem();
+ final JComboBox<String> attCombo = populateAttributesDropdown(
+ featureType, attNames);
JComboBox<Condition> condCombo = new JComboBox<>();
JTextField patternField = new JTextField(8);
{
if (validateFilter(patternField, condCombo))
{
- updateFilter(attCombo, condCombo, patternField, i);
+ updateFilter(attCombo, condCombo, patternField, filterIndex);
filtersChanged();
}
}
}
};
- /*
- * drop-down choice of attribute
- */
- if (attNames.isEmpty())
+ if ("".equals(attribute))
{
- attCombo.addItem("---");
- attCombo.setToolTipText(MessageManager
- .getString("label.no_attributes_known"));
+ attCombo.setSelectedItem(null);
}
else
{
- attCombo.setToolTipText("");
- for (String attName : attNames)
- {
- attCombo.addItem(attName);
- }
- if ("".equals(attribute))
- {
- attCombo.setSelectedItem(null);
- }
- else
- {
- attCombo.setSelectedItem(attribute);
- }
- attCombo.addItemListener(itemListener);
+ attCombo.setSelectedItem(attribute);
}
+ attCombo.addItemListener(itemListener);
+
filterRow.add(attCombo);
/*
@Override
public void actionPerformed(ActionEvent e)
{
- filters.remove(i);
+ filters.remove(filterIndex);
filtersChanged();
}
});
}
/**
+ * A helper method to build the drop-down choice of attributes for a feature.
+ * Where metadata is available with a description for an attribute, that is
+ * added as a tooltip.
+ *
+ * @param featureType
+ * @param attNames
+ */
+ protected JComboBox<String> populateAttributesDropdown(
+ String featureType, List<String> attNames)
+ {
+ List<String> tooltips = new ArrayList<>();
+ FeatureAttributes fa = FeatureAttributes.getInstance();
+ for (String attName : attNames)
+ {
+ String desc = fa.getDescription(featureType, attName);
+ if (desc != null && desc.length() > MAX_TOOLTIP_LENGTH)
+ {
+ desc = desc.substring(0, MAX_TOOLTIP_LENGTH) + "...";
+ }
+ tooltips.add(desc == null ? "" : desc);
+ }
+
+ JComboBox<String> attCombo = JvSwingUtils.buildComboWithTooltips(
+ attNames, tooltips);
+ if (attNames.isEmpty())
+ {
+ attCombo.setToolTipText(MessageManager
+ .getString("label.no_attributes"));
+ }
+ return attCombo;
+ }
+
+ /**
* Action on any change to feature filtering, namely
* <ul>
* <li>change of selected attribute</li>
boolean isSelected, boolean hasFocus, int row, int column)
{
FeatureColourI cellColour = (FeatureColourI) color;
- // JLabel comp = new JLabel();
- // comp.
setOpaque(true);
- // comp.
- // setBounds(getBounds());
- Color newColor;
setToolTipText(baseTT);
setBackground(tbl.getBackground());
if (!cellColour.isSimpleColour())
Rectangle cr = tbl.getCellRect(row, column, false);
FeatureSettings.renderGraduatedColor(this, cellColour,
(int) cr.getWidth(), (int) cr.getHeight());
-
}
else
{
this.setText("");
this.setIcon(null);
- newColor = cellColour.getColour();
- setBackground(newColor);
+ setBackground(cellColour.getColour());
}
if (isSelected)
{
int w, int h)
{
boolean thr = false;
- String tt = "";
- String tx = "";
+ StringBuilder tt = new StringBuilder();
+ StringBuilder tx = new StringBuilder();
+
+ if (gcol.isColourByAttribute())
+ {
+ tx.append(gcol.getAttributeName());
+ }
+ else if (!gcol.isColourByLabel())
+ {
+ tx.append(MessageManager.getString("label.score"));
+ }
+ tx.append(" ");
if (gcol.isAboveThreshold())
{
thr = true;
- tx += ">";
- tt += "Thresholded (Above " + gcol.getThreshold() + ") ";
+ tx.append(">");
+ tt.append("Thresholded (Above ").append(gcol.getThreshold())
+ .append(") ");
}
if (gcol.isBelowThreshold())
{
thr = true;
- tx += "<";
- tt += "Thresholded (Below " + gcol.getThreshold() + ") ";
+ tx.append("<");
+ tt.append("Thresholded (Below ").append(gcol.getThreshold())
+ .append(") ");
}
if (gcol.isColourByLabel())
{
- tt = "Coloured by label text. " + tt;
+ tt.append("Coloured by label text. ").append(tt);
if (thr)
{
- tx += " ";
+ tx.append(" ");
+ }
+ if (!gcol.isColourByAttribute())
+ {
+ tx.append("Label");
}
- tx += "Label";
comp.setIcon(null);
}
else
// + ", " + minCol.getBlue() + ")");
}
comp.setHorizontalAlignment(SwingConstants.CENTER);
- comp.setText(tx);
+ comp.setText(tx.toString());
if (tt.length() > 0)
{
if (comp.getToolTipText() == null)
{
- comp.setToolTipText(tt);
+ comp.setToolTipText(tt.toString());
}
else
{
- comp.setToolTipText(tt + " " + comp.getToolTipText());
+ comp.setToolTipText(tt.append(" ").append(comp.getToolTipText())
+ .toString());
}
}
}
button.setBorderPainted(false);
// Set up the dialog that the button brings up.
colorChooser = new JColorChooser();
- dialog = JColorChooser.createDialog(button, "Select new Colour", true, // modal
+ dialog = JColorChooser.createDialog(button,
+ MessageManager.getString("label.select_new_colour"), true, // modal
colorChooser, this, // OK button handler
null); // no CANCEL button handler
}
SequenceI sequence = av.getAlignment().getSequenceAt(seq);
StringBuilder tip = new StringBuilder(64);
seqAnnotReport.createTooltipAnnotationReport(tip, sequence,
- av.isShowDBRefs(), av.isShowNPFeats(),
- sp.seqCanvas.fr.getMinMax());
+ av.isShowDBRefs(), av.isShowNPFeats(), sp.seqCanvas.fr);
setToolTipText(JvSwingUtils.wrapTooltip(true,
sequence.getDisplayId(true) + " " + tip.toString()));
}
import java.awt.BorderLayout;
import java.awt.Color;
+import java.awt.Component;
import java.awt.Font;
import java.awt.GridLayout;
import java.awt.Rectangle;
import java.awt.event.ActionListener;
+import java.awt.event.MouseAdapter;
+import java.awt.event.MouseEvent;
+import java.util.List;
import java.util.Objects;
import javax.swing.AbstractButton;
import javax.swing.JButton;
+import javax.swing.JComboBox;
import javax.swing.JComponent;
import javax.swing.JLabel;
import javax.swing.JMenu;
comp.setFont(JvSwingUtils.getLabelFont());
}
+ /**
+ * A helper method to build a drop-down choice of values, with tooltips for
+ * the entries
+ *
+ * @param entries
+ * @param tooltips
+ */
+ public static JComboBox<String> buildComboWithTooltips(
+ List<String> entries, List<String> tooltips)
+ {
+ JComboBox<String> combo = new JComboBox<>();
+ final ComboBoxTooltipRenderer renderer = new ComboBoxTooltipRenderer();
+ combo.setRenderer(renderer);
+ for (String attName : entries)
+ {
+ combo.addItem(attName);
+ }
+ renderer.setTooltips(tooltips);
+ final MouseAdapter mouseListener = new MouseAdapter()
+ {
+ @Override
+ public void mouseEntered(MouseEvent e)
+ {
+ int j = combo.getSelectedIndex();
+ if (j > -1)
+ {
+ combo.setToolTipText(tooltips.get(j));
+ }
+ }
+ @Override
+ public void mouseExited(MouseEvent e)
+ {
+ combo.setToolTipText(null);
+ }
+ };
+ for (Component c : combo.getComponents())
+ {
+ c.addMouseListener(mouseListener);
+ }
+ return combo;
+ }
+
}
new Object[]
{ seq.getDisplayId(true) }) + "</h2></p><p>");
new SequenceAnnotationReport(null).createSequenceAnnotationReport(
- contents, seq, true, true,
- (ap.getSeqPanel().seqCanvas.fr != null)
- ? ap.getSeqPanel().seqCanvas.fr.getMinMax()
- : null);
+ contents, seq, true, true, ap.getSeqPanel().seqCanvas.fr);
contents.append("</p>");
}
cap.setText("<html>" + contents.toString() + "</html>");
public class SeqPanel extends JPanel
implements MouseListener, MouseMotionListener, MouseWheelListener,
SequenceListener, SelectionListener
-
{
- /** DOCUMENT ME!! */
+ private static final int MAX_TOOLTIP_LENGTH = 300;
+
public SeqCanvas seqCanvas;
- /** DOCUMENT ME!! */
public AlignmentPanel ap;
/*
SearchResultsI lastSearchResults;
/**
- * Creates a new SeqPanel object.
+ * Creates a new SeqPanel object
*
- * @param avp
- * DOCUMENT ME!
- * @param p
- * DOCUMENT ME!
+ * @param viewport
+ * @param alignPanel
*/
- public SeqPanel(AlignViewport av, AlignmentPanel ap)
+ public SeqPanel(AlignViewport viewport, AlignmentPanel alignPanel)
{
linkImageURL = getClass().getResource("/images/link.gif");
seqARep = new SequenceAnnotationReport(linkImageURL.toString());
ToolTipManager.sharedInstance().registerComponent(this);
ToolTipManager.sharedInstance().setInitialDelay(0);
ToolTipManager.sharedInstance().setDismissDelay(10000);
- this.av = av;
+ this.av = viewport;
setBackground(Color.white);
- seqCanvas = new SeqCanvas(ap);
+ seqCanvas = new SeqCanvas(alignPanel);
setLayout(new BorderLayout());
add(seqCanvas, BorderLayout.CENTER);
- this.ap = ap;
+ this.ap = alignPanel;
- if (!av.isDataset())
+ if (!viewport.isDataset())
{
addMouseMotionListener(this);
addMouseListener(this);
addMouseWheelListener(this);
- ssm = av.getStructureSelectionManager();
+ ssm = viewport.getStructureSelectionManager();
ssm.addStructureViewerListener(this);
ssm.addSelectionListener(this);
}
List<SequenceFeature> features = ap.getFeatureRenderer()
.findFeaturesAtColumn(sequence, column + 1);
seqARep.appendFeatures(tooltipText, pos, features,
- this.ap.getSeqPanel().seqCanvas.fr.getMinMax());
+ this.ap.getSeqPanel().seqCanvas.fr);
}
if (tooltipText.length() == 6) // <html>
{
}
else
{
+ if (tooltipText.length() > MAX_TOOLTIP_LENGTH) // constant
+ {
+ tooltipText.setLength(MAX_TOOLTIP_LENGTH);
+ tooltipText.append("...");
+ }
String textString = tooltipText.toString();
if (lastTooltip == null || !lastTooltip.equals(textString))
{
*/
package jalview.io;
+import jalview.api.FeatureColourI;
import jalview.datamodel.DBRefEntry;
import jalview.datamodel.DBRefSource;
import jalview.datamodel.SequenceFeature;
import jalview.datamodel.SequenceI;
-import jalview.io.gff.GffConstants;
import jalview.util.MessageManager;
import jalview.util.StringUtils;
import jalview.util.UrlLink;
+import jalview.viewmodel.seqfeatures.FeatureRendererModel;
import java.util.Arrays;
import java.util.Collection;
{
return 1;
}
- int comp = s1 == null ? -1
- : (s2 == null ? 1 : s1.compareToIgnoreCase(s2));
+ int comp = s1 == null ? -1 : (s2 == null ? 1 : s1
+ .compareToIgnoreCase(s2));
if (comp == 0)
{
String a1 = ref1.getAccessionId();
String a2 = ref2.getAccessionId();
- comp = a1 == null ? -1
- : (a2 == null ? 1 : a1.compareToIgnoreCase(a2));
+ comp = a1 == null ? -1 : (a2 == null ? 1 : a1
+ .compareToIgnoreCase(a2));
}
return comp;
}
}
};
- public SequenceAnnotationReport(String linkImageURL)
+ public SequenceAnnotationReport(String linkURL)
{
- this.linkImageURL = linkImageURL;
+ this.linkImageURL = linkURL;
}
/**
* @param minmax
*/
public void appendFeatures(final StringBuilder sb, int rpos,
- List<SequenceFeature> features, Map<String, float[][]> minmax)
+ List<SequenceFeature> features, FeatureRendererModel fr)
{
if (features != null)
{
for (SequenceFeature feature : features)
{
- appendFeature(sb, rpos, minmax, feature);
+ appendFeature(sb, rpos, fr, feature);
}
}
}
* @param feature
*/
void appendFeature(final StringBuilder sb, int rpos,
- Map<String, float[][]> minmax, SequenceFeature feature)
+ FeatureRendererModel fr, SequenceFeature feature)
{
if (feature.isContactFeature())
{
sb.append(feature.getType()).append(" ").append(feature.getBegin())
.append(":").append(feature.getEnd());
}
+ return;
}
- else
+
+ if (sb.length() > 6)
+ {
+ sb.append("<br>");
+ }
+ // TODO: remove this hack to display link only features
+ boolean linkOnly = feature.getValue("linkonly") != null;
+ if (!linkOnly)
{
- if (sb.length() > 6)
+ sb.append(feature.getType()).append(" ");
+ if (rpos != 0)
{
- sb.append("<br>");
+ // we are marking a positional feature
+ sb.append(feature.begin);
}
- // TODO: remove this hack to display link only features
- boolean linkOnly = feature.getValue("linkonly") != null;
- if (!linkOnly)
+ if (feature.begin != feature.end)
{
- sb.append(feature.getType()).append(" ");
- if (rpos != 0)
- {
- // we are marking a positional feature
- sb.append(feature.begin);
- }
- if (feature.begin != feature.end)
- {
- sb.append(" ").append(feature.end);
- }
+ sb.append(" ").append(feature.end);
+ }
- String description = feature.getDescription();
- if (description != null && !description.equals(feature.getType()))
- {
- description = StringUtils.stripHtmlTags(description);
- sb.append("; ").append(description);
- }
- // check score should be shown
- if (!Float.isNaN(feature.getScore()))
+ String description = feature.getDescription();
+ if (description != null && !description.equals(feature.getType()))
+ {
+ description = StringUtils.stripHtmlTags(description);
+ sb.append("; ").append(description);
+ }
+
+ if (showScore(feature, fr))
+ {
+ sb.append(" Score=").append(String.valueOf(feature.getScore()));
+ }
+ String status = (String) feature.getValue("status");
+ if (status != null && status.length() > 0)
+ {
+ sb.append("; (").append(status).append(")");
+ }
+
+ /*
+ * add attribute value if coloured by attribute
+ */
+ if (fr != null)
+ {
+ FeatureColourI fc = fr.getFeatureColours().get(feature.getType());
+ if (fc != null && fc.isColourByAttribute())
{
- float[][] rng = (minmax == null) ? null
- : minmax.get(feature.getType());
- if (rng != null && rng[0] != null && rng[0][0] != rng[0][1])
+ String attName = fc.getAttributeName();
+ String attVal = feature.getValueAsString(attName);
+ if (attVal != null)
{
- sb.append(" Score=").append(String.valueOf(feature.getScore()));
+ sb.append("; ").append(attName).append("=").append(attVal);
}
}
- String status = (String) feature.getValue("status");
- if (status != null && status.length() > 0)
- {
- sb.append("; (").append(status).append(")");
- }
- String clinSig = (String) feature
- .getValue(GffConstants.CLINICAL_SIGNIFICANCE);
- if (clinSig != null)
- {
- sb.append("; ").append(clinSig);
- }
}
}
}
/**
+ * Answers true if score should be shown, else false. Score is shown if it is
+ * not NaN, and the feature type has a non-trivial min-max score range
+ */
+ boolean showScore(SequenceFeature feature, FeatureRendererModel fr)
+ {
+ if (Float.isNaN(feature.getScore()))
+ {
+ return false;
+ }
+ if (fr == null)
+ {
+ return true;
+ }
+ float[][] minMax = fr.getMinMax().get(feature.getType());
+
+ /*
+ * minMax[0] is the [min, max] score range for positional features
+ */
+ if (minMax == null || minMax[0] == null || minMax[0][0] == minMax[0][1])
+ {
+ return false;
+ }
+ return true;
+ }
+
+ /**
* Format and appends any hyperlinks for the sequence feature to the string
* buffer
*
{
for (List<String> urllink : createLinksFrom(null, urlstring))
{
- sb.append("<br/> <a href=\"" + urllink.get(3) + "\" target=\""
- + urllink.get(0) + "\">"
+ sb.append("<br/> <a href=\""
+ + urllink.get(3)
+ + "\" target=\""
+ + urllink.get(0)
+ + "\">"
+ (urllink.get(0).toLowerCase()
- .equals(urllink.get(1).toLowerCase())
- ? urllink.get(0)
- : (urllink.get(0) + ":"
- + urllink.get(1)))
- + "</a></br>");
+ .equals(urllink.get(1).toLowerCase()) ? urllink
+ .get(0) : (urllink.get(0) + ":" + urllink
+ .get(1))) + "</a></br>");
}
} catch (Exception x)
{
- System.err.println(
- "problem when creating links from " + urlstring);
+ System.err.println("problem when creating links from "
+ + urlstring);
x.printStackTrace();
}
}
public void createSequenceAnnotationReport(final StringBuilder tip,
SequenceI sequence, boolean showDbRefs, boolean showNpFeats,
- Map<String, float[][]> minmax)
+ FeatureRendererModel fr)
{
createSequenceAnnotationReport(tip, sequence, showDbRefs, showNpFeats,
- minmax, false);
+ fr, false);
}
/**
* whether to include database references for the sequence
* @param showNpFeats
* whether to include non-positional sequence features
- * @param minmax
+ * @param fr
* @param summary
* @return
*/
int createSequenceAnnotationReport(final StringBuilder sb,
SequenceI sequence, boolean showDbRefs, boolean showNpFeats,
- Map<String, float[][]> minmax, boolean summary)
+ FeatureRendererModel fr, boolean summary)
{
String tmp;
sb.append("<i>");
{
ds = ds.getDatasetSequence();
}
-
+
if (showDbRefs)
{
maxWidth = Math.max(maxWidth, appendDbRefs(sb, ds, summary));
.getNonPositionalFeatures())
{
int sz = -sb.length();
- appendFeature(sb, 0, minmax, sf);
+ appendFeature(sb, 0, fr, sf);
sz += sb.length();
maxWidth = Math.max(maxWidth, sz);
}
}
if (moreSources)
{
- sb.append("<br>").append(source)
- .append(COMMA).append(ELLIPSIS);
+ sb.append("<br>").append(source).append(COMMA).append(ELLIPSIS);
}
if (ellipsis)
{
public void createTooltipAnnotationReport(final StringBuilder tip,
SequenceI sequence, boolean showDbRefs, boolean showNpFeats,
- Map<String, float[][]> minmax)
+ FeatureRendererModel fr)
{
- int maxWidth = createSequenceAnnotationReport(tip, sequence, showDbRefs,
- showNpFeats, minmax, true);
+ int maxWidth = createSequenceAnnotationReport(tip, sequence,
+ showDbRefs, showNpFeats, fr, true);
if (maxWidth > 60)
{
List<SequenceFeature> overlaps = seq.getFeatures().findFeatures(
visiblePositions.getBegin(), visiblePositions.getEnd(), type);
- // filterFeaturesForDisplay(overlaps, fc);
+ if (fc.isSimpleColour())
+ {
+ filterFeaturesForDisplay(overlaps);
+ }
for (SequenceFeature sf : overlaps)
{
import java.util.StringTokenizer;
/**
- * A class that wraps either a simple colour or a graduated colour
+ * A class that represents a colour scheme for a feature type. Options supported
+ * are currently
+ * <ul>
+ * <li>a simple colour e.g. Red</li>
+ * <li>colour by label - a colour is generated from the feature description</li>
+ * <li>graduated colour by feature score</li>
+ * <ul>
+ * <li>minimum and maximum score range must be provided</li>
+ * <li>minimum and maximum value colours should be specified</li>
+ * <li>a colour for 'no value' may optionally be provided</li>
+ * <li>colours for intermediate scores are interpolated RGB values</li>
+ * <li>there is an optional threshold above/below which to colour values</li>
+ * <li>the range may be the full value range, or may be limited by the threshold
+ * value</li>
+ * </ul>
+ * <li>colour by (text) value of a named attribute</li> <li>graduated colour by
+ * (numeric) value of a named attribute</li> </ul>
*/
public class FeatureColour implements FeatureColourI
{
+ static final Color DEFAULT_NO_COLOUR = Color.LIGHT_GRAY;
+
private static final String BAR = "|";
final private Color colour;
final private Color maxColour;
+ /*
+ * colour to use for colour by attribute when the
+ * attribute value is absent
+ */
+ final private Color noColour;
+
+ /*
+ * if true, then colour has a gradient based on a numerical
+ * range (either feature score, or an attribute value)
+ */
private boolean graduatedColour;
+ /*
+ * if true, colour values are generated from a text string,
+ * either feature description, or an attribute value
+ */
private boolean colourByLabel;
+ /*
+ * if not null, the value of this named attribute is used for
+ * colourByLabel or graduatedColour
+ */
+ private String byAttributeName;
+
private float threshold;
private float base;
{
minColour = Color.WHITE;
maxColour = Color.BLACK;
+ noColour = DEFAULT_NO_COLOUR;
minRed = 0f;
minGreen = 0f;
minBlue = 0f;
}
/**
- * Constructor given a colour range and a score range
+ * Constructor given a colour range and a score range, defaulting 'no value
+ * colour' to be the same as minimum colour
*
* @param low
* @param high
*/
public FeatureColour(Color low, Color high, float min, float max)
{
- if (low == null)
- {
- low = Color.white;
- }
- if (high == null)
- {
- high = Color.black;
- }
- graduatedColour = true;
- colour = null;
- minColour = low;
- maxColour = high;
- threshold = Float.NaN;
- isHighToLow = min >= max;
- minRed = low.getRed() / 255f;
- minGreen = low.getGreen() / 255f;
- minBlue = low.getBlue() / 255f;
- deltaRed = (high.getRed() / 255f) - minRed;
- deltaGreen = (high.getGreen() / 255f) - minGreen;
- deltaBlue = (high.getBlue() / 255f) - minBlue;
- if (isHighToLow)
- {
- base = max;
- range = min - max;
- }
- else
- {
- base = min;
- range = max - min;
- }
+ this(low, high, low, min, max);
}
/**
colour = fc.colour;
minColour = fc.minColour;
maxColour = fc.maxColour;
+ noColour = fc.noColour;
minRed = fc.minRed;
minGreen = fc.minGreen;
minBlue = fc.minBlue;
base = fc.base;
range = fc.range;
isHighToLow = fc.isHighToLow;
+ byAttributeName = fc.byAttributeName;
setAboveThreshold(fc.isAboveThreshold());
setBelowThreshold(fc.isBelowThreshold());
setThreshold(fc.getThreshold());
public FeatureColour(FeatureColour fc, float min, float max)
{
this(fc);
- graduatedColour = true;
+ setGraduatedColour(true);
updateBounds(min, max);
}
+ public FeatureColour(Color low, Color high, Color noValueColour,
+ float min, float max)
+ {
+ if (low == null)
+ {
+ low = Color.white;
+ }
+ if (high == null)
+ {
+ high = Color.black;
+ }
+ graduatedColour = true;
+ colour = null;
+ minColour = low;
+ maxColour = high;
+ noColour = noValueColour;
+ threshold = Float.NaN;
+ isHighToLow = min >= max;
+ minRed = low.getRed() / 255f;
+ minGreen = low.getGreen() / 255f;
+ minBlue = low.getBlue() / 255f;
+ deltaRed = (high.getRed() / 255f) - minRed;
+ deltaGreen = (high.getGreen() / 255f) - minGreen;
+ deltaBlue = (high.getBlue() / 255f) - minBlue;
+ if (isHighToLow)
+ {
+ base = max;
+ range = min - max;
+ }
+ else
+ {
+ base = min;
+ range = max - min;
+ }
+ }
+
@Override
public boolean isGraduatedColour()
{
}
@Override
+ public Color getNoColour()
+ {
+ return noColour;
+ }
+
+ @Override
public boolean isColourByLabel()
{
return colourByLabel;
}
/**
- * Updates the base and range appropriately for the given minmax range
- *
- * @param min
- * @param max
+ * {@inheritDoc}
*/
@Override
public void updateBounds(float min, float max)
{
if (isColourByLabel())
{
- return ColorUtils.createColourFromName(feature.getDescription());
+ String label = byAttributeName == null ? feature.getDescription()
+ : feature.getValueAsString(byAttributeName);
+ return label == null ? noColour : ColorUtils
+ .createColourFromName(label);
}
if (!isGraduatedColour())
/*
* graduated colour case, optionally with threshold
+ * may be based on feature score on an attribute value
* Float.NaN is assigned minimum visible score colour
+ * no such attribute is assigned the 'no value' colour
*/
float scr = feature.getScore();
+ if (byAttributeName != null)
+ {
+ try
+ {
+ String attVal = feature.getValueAsString(byAttributeName);
+ scr = Float.valueOf(attVal);
+ } catch (Throwable e)
+ {
+ scr = Float.NaN;
+ }
+ }
if (Float.isNaN(scr))
{
- return getMinColour();
+ return noColour;
}
+
if (isAboveThreshold() && scr <= threshold)
{
return null;
}
+
if (isBelowThreshold() && scr >= threshold)
{
return null;
return String.format("%s\t%s", featureType, colourString);
}
+ @Override
+ public boolean isColourByAttribute()
+ {
+ return byAttributeName != null;
+ }
+
+ @Override
+ public String getAttributeName()
+ {
+ return byAttributeName;
+ }
+
+ @Override
+ public void setAttributeName(String name)
+ {
+ byAttributeName = name;
+ }
+
}
*/
private void upgradeOldLinks(HashMap<String, UrlLink> urls)
{
+ boolean upgrade = false;
// upgrade old SRS link
if (urls.containsKey(SRS_LABEL))
{
urls.remove(SRS_LABEL);
+ upgrade = true;
+ }
+ // upgrade old EBI link - easier just to remove and re-add than faffing
+ // around checking exact url
+ if (urls.containsKey(UrlConstants.DEFAULT_LABEL))
+ {
+ // note because this is called separately for selected and nonselected
+ // urls, the default url will not always be present
+ urls.remove(UrlConstants.DEFAULT_LABEL);
+ upgrade = true;
+ }
+ if (upgrade)
+ {
UrlLink link = new UrlLink(UrlConstants.DEFAULT_STRING);
link.setLabel(UrlConstants.DEFAULT_LABEL);
urls.put(UrlConstants.DEFAULT_LABEL, link);
package jalview.util;
import java.awt.Color;
+import java.util.HashMap;
+import java.util.Map;
import java.util.Random;
public class ColorUtils
{
+ private static final int MAX_CACHE_SIZE = 1729;
+ /*
+ * a cache for colours generated from text strings
+ */
+ static Map<String, Color> myColours = new HashMap<>();
/**
* Generates a random color, will mix with input color. Code taken from
{
return Color.white;
}
+ if (myColours.containsKey(name))
+ {
+ return myColours.get(name);
+ }
int lsize = name.length();
int start = 0;
int end = lsize / 3;
Color color = new Color(r, g, b);
+ if (myColours.size() < MAX_CACHE_SIZE)
+ {
+ myColours.put(name, color);
+ }
+
return color;
}
public static final String DEFAULT_STRING = DEFAULT_LABEL
+ "|https://www.ebi.ac.uk/ebisearch/search.ebi?db=allebi&query=$SEQUENCE_ID$";
+ private static final String COLON = ":";
+
/*
* not instantiable
*/
private UrlConstants()
{
}
+
+ public static boolean isDefaultString(String link)
+ {
+ String sublink = link.substring(link.indexOf(COLON) + 1);
+ String subdefault = DEFAULT_STRING
+ .substring(DEFAULT_STRING.indexOf(COLON) + 1);
+ return sublink.equalsIgnoreCase(subdefault);
+ }
}
{
StringBuilder sb = new StringBuilder();
sb.append(key).append(" ").append(matcher.getCondition().toString())
- .append(" ").append(matcher.getPattern());
+ .append(" ");
+ if (matcher.getCondition().isNumeric())
+ {
+ sb.append(matcher.getPattern());
+ }
+ else
+ {
+ sb.append("'").append(matcher.getPattern()).append("'");
+ }
return sb.toString();
}
package jalview.util.matcher;
import java.util.ArrayList;
-import java.util.Iterator;
import java.util.List;
import java.util.function.Function;
}
@Override
- public Iterator<KeyedMatcherI> getMatchers()
+ public Iterable<KeyedMatcherI> getMatchers()
{
- return matchConditions.iterator();
+ return matchConditions;
}
@Override
package jalview.util.matcher;
-import java.util.Iterator;
import java.util.function.Function;
/**
*
* @return
*/
- Iterator<KeyedMatcherI> getMatchers();
+ Iterable<KeyedMatcherI> getMatchers();
/**
* Answers true if this object contains no conditions
@Override
public String toString()
{
- return condition.name() + " " + pattern;
+ StringBuilder sb = new StringBuilder();
+ sb.append(condition.name()).append(" ");
+ if (condition.isNumeric())
+ {
+ sb.append(pattern);
+ }
+ else
+ {
+ sb.append("'").append(pattern).append("'");
+ }
+
+ return sb.toString();
}
}
if (mmrange != null)
{
FeatureColourI fc = featureColours.get(oldRender[j]);
- if (fc != null && !fc.isSimpleColour() && fc.isAutoScaled())
+ if (fc != null && !fc.isSimpleColour() && fc.isAutoScaled()
+ && !fc.isColourByAttribute())
{
fc.updateBounds(mmrange[0][0], mmrange[0][1]);
}
if (mmrange != null)
{
FeatureColourI fc = featureColours.get(newf[i]);
- if (fc != null && !fc.isSimpleColour() && fc.isAutoScaled())
+ if (fc != null && !fc.isSimpleColour() && fc.isAutoScaled()
+ && !fc.isColourByAttribute())
{
fc.updateBounds(mmrange[0][0], mmrange[0][1]);
}
*/
protected boolean showFeatureOfType(String type)
{
- return type == null ? false : av.getFeaturesDisplayed().isVisible(type);
+ return type == null ? false : (av.getFeaturesDisplayed() == null ? true
+ : av.getFeaturesDisplayed().isVisible(type));
}
@Override
/**
* Removes from the list of features any that duplicate the location of a
- * feature of the same type (unless feature is filtered out, or a graduated
- * colour scheme or colour by label is applied). Should be used only for
- * features of the same feature colour (which normally implies the same
- * feature type).
+ * feature of the same type. Should be used only for features of the same,
+ * simple, feature colour (which normally implies the same feature type). Does
+ * not check visibility settings for feature type or feature group.
*
* @param features
- * @param fc
*/
- public void filterFeaturesForDisplay(List<SequenceFeature> features,
- FeatureColourI fc)
+ public void filterFeaturesForDisplay(List<SequenceFeature> features)
{
if (features.isEmpty())
{
return;
}
SequenceFeatures.sortFeatures(features, true);
- boolean simpleColour = fc == null || fc.isSimpleColour();
SequenceFeature lastFeature = null;
Iterator<SequenceFeature> it = features.iterator();
* (checking type and isContactFeature as a fail-safe here, although
* currently they are guaranteed to match in this context)
*/
- if (simpleColour)
+ if (lastFeature != null && sf.getBegin() == lastFeature.getBegin()
+ && sf.getEnd() == lastFeature.getEnd()
+ && sf.isContactFeature() == lastFeature.isContactFeature()
+ && sf.getType().equals(lastFeature.getType()))
{
- if (lastFeature != null && sf.getBegin() == lastFeature.getBegin()
- && sf.getEnd() == lastFeature.getEnd()
- && sf.isContactFeature() == lastFeature.isContactFeature()
- && sf.getType().equals(lastFeature.getType()))
- {
- it.remove();
- }
+ it.remove();
}
lastFeature = sf;
}
import static org.testng.AssertJUnit.assertEquals;
import static org.testng.AssertJUnit.assertTrue;
+import jalview.api.FeatureColourI;
import jalview.datamodel.DBRefEntry;
import jalview.datamodel.Sequence;
import jalview.datamodel.SequenceFeature;
import jalview.datamodel.SequenceI;
import jalview.gui.JvOptionPane;
import jalview.io.gff.GffConstants;
+import jalview.renderer.seqfeatures.FeatureRenderer;
+import jalview.schemes.FeatureColour;
+import jalview.viewmodel.seqfeatures.FeatureRendererModel;
-import java.util.HashMap;
-import java.util.Hashtable;
+import java.awt.Color;
import java.util.Map;
import junit.extensions.PA;
SequenceFeature sf = new SequenceFeature("METAL", "Fe2-S", 1, 3, 1.3f,
"group");
- Map<String, float[][]> minmax = new Hashtable<String, float[][]>();
- sar.appendFeature(sb, 1, minmax, sf);
+ FeatureRendererModel fr = new FeatureRenderer(null);
+ Map<String, float[][]> minmax = fr.getMinMax();
+ sar.appendFeature(sb, 1, fr, sf);
/*
* map has no entry for this feature type - score is not shown:
*/
* map has entry for this feature type - score is shown:
*/
minmax.put("METAL", new float[][] { { 0f, 1f }, null });
- sar.appendFeature(sb, 1, minmax, sf);
+ sar.appendFeature(sb, 1, fr, sf);
// <br> is appended to a buffer > 6 in length
assertEquals("METAL 1 3; Fe2-S<br>METAL 1 3; Fe2-S Score=1.3",
sb.toString());
*/
minmax.put("METAL", new float[][] { { 2f, 2f }, null });
sb.setLength(0);
- sar.appendFeature(sb, 1, minmax, sf);
+ sar.appendFeature(sb, 1, fr, sf);
assertEquals("METAL 1 3; Fe2-S", sb.toString());
}
assertEquals("METAL 1 3; Fe2-S", sb.toString());
}
+ /**
+ * A specific attribute value is included if it is used to colour the feature
+ */
@Test(groups = "Functional")
- public void testAppendFeature_clinicalSignificance()
+ public void testAppendFeature_colouredByAttribute()
{
SequenceAnnotationReport sar = new SequenceAnnotationReport(null);
StringBuilder sb = new StringBuilder();
Float.NaN, "group");
sf.setValue("clinical_significance", "Benign");
- sar.appendFeature(sb, 1, null, sf);
- assertEquals("METAL 1 3; Fe2-S; Benign", sb.toString());
+ /*
+ * first with no colour by attribute
+ */
+ FeatureRendererModel fr = new FeatureRenderer(null);
+ sar.appendFeature(sb, 1, fr, sf);
+ assertEquals("METAL 1 3; Fe2-S", sb.toString());
+
+ /*
+ * then with colour by an attribute the feature lacks
+ */
+ FeatureColourI fc = new FeatureColour(Color.white, Color.black, 5, 10);
+ fc.setAttributeName("Pfam");
+ fr.setColour("METAL", fc);
+ sb.setLength(0);
+ sar.appendFeature(sb, 1, fr, sf);
+ assertEquals("METAL 1 3; Fe2-S", sb.toString()); // no change
+
+ /*
+ * then with colour by an attribute the feature has
+ */
+ fc.setAttributeName("clinical_significance");
+ sb.setLength(0);
+ sar.appendFeature(sb, 1, fr, sf);
+ assertEquals("METAL 1 3; Fe2-S; clinical_significance=Benign",
+ sb.toString());
}
@Test(groups = "Functional")
- public void testAppendFeature_withScoreStatusClinicalSignificance()
+ public void testAppendFeature_withScoreStatusAttribute()
{
SequenceAnnotationReport sar = new SequenceAnnotationReport(null);
StringBuilder sb = new StringBuilder();
"group");
sf.setStatus("Confirmed");
sf.setValue("clinical_significance", "Benign");
- Map<String, float[][]> minmax = new Hashtable<String, float[][]>();
+
+ FeatureRendererModel fr = new FeatureRenderer(null);
+ Map<String, float[][]> minmax = fr.getMinMax();
+ FeatureColourI fc = new FeatureColour(Color.white, Color.blue, 12, 22);
+ fc.setAttributeName("clinical_significance");
+ fr.setColour("METAL", fc);
minmax.put("METAL", new float[][] { { 0f, 1f }, null });
- sar.appendFeature(sb, 1, minmax, sf);
+ sar.appendFeature(sb, 1, fr, sf);
- assertEquals("METAL 1 3; Fe2-S Score=1.3; (Confirmed); Benign",
+ assertEquals(
+ "METAL 1 3; Fe2-S Score=1.3; (Confirmed); clinical_significance=Benign",
sb.toString());
}
null));
sb.setLength(0);
sar.createSequenceAnnotationReport(sb, seq, true, true, null);
- String expected = "<i><br>SeqDesc<br>Type1 ; Nonpos</i>";
+ String expected = "<i><br>SeqDesc<br>Type1 ; Nonpos Score=1.0</i>";
assertEquals(expected, sb.toString());
/*
*/
seq.addSequenceFeature(new SequenceFeature("Metal", "Desc", 0, 0, 5f,
null));
- Map<String, float[][]> minmax = new HashMap<String, float[][]>();
+
+ FeatureRendererModel fr = new FeatureRenderer(null);
+ Map<String, float[][]> minmax = fr.getMinMax();
minmax.put("Metal", new float[][] { null, new float[] { 2f, 5f } });
+
sb.setLength(0);
- sar.createSequenceAnnotationReport(sb, seq, true, true, minmax);
+ sar.createSequenceAnnotationReport(sb, seq, true, true, fr);
expected = "<i><br>SeqDesc<br>Metal ; Desc<br>Type1 ; Nonpos</i>";
assertEquals(expected, sb.toString());
sf.setValue("linkonly", Boolean.TRUE);
seq.addSequenceFeature(sf);
sb.setLength(0);
- sar.createSequenceAnnotationReport(sb, seq, true, true, minmax);
+ sar.createSequenceAnnotationReport(sb, seq, true, true, fr);
assertEquals(expected, sb.toString()); // unchanged!
/*
- * 'clinical_significance' currently being specially included
+ * 'clinical_significance' attribute only included when
+ * used for feature colouring
*/
SequenceFeature sf2 = new SequenceFeature("Variant", "Havana", 0, 0,
5f, null);
sf2.setValue(GffConstants.CLINICAL_SIGNIFICANCE, "benign");
seq.addSequenceFeature(sf2);
sb.setLength(0);
- sar.createSequenceAnnotationReport(sb, seq, true, true, minmax);
- expected = "<i><br>SeqDesc<br>Metal ; Desc<br>Type1 ; Nonpos<br>Variant ; Havana; benign</i>";
+ sar.createSequenceAnnotationReport(sb, seq, true, true, fr);
+ expected = "<i><br>SeqDesc<br>Metal ; Desc<br>Type1 ; Nonpos<br>Variant ; Havana</i>";
assertEquals(expected, sb.toString());
/*
*/
seq.addDBRef(new DBRefEntry("PDB", "0", "3iu1"));
seq.addDBRef(new DBRefEntry("Uniprot", "1", "P30419"));
+
// with showDbRefs = false
sb.setLength(0);
- sar.createSequenceAnnotationReport(sb, seq, false, true, minmax);
+ sar.createSequenceAnnotationReport(sb, seq, false, true, fr);
assertEquals(expected, sb.toString()); // unchanged
- // with showDbRefs = true
+
+ // with showDbRefs = true, colour Variant features by clinical_significance
sb.setLength(0);
- sar.createSequenceAnnotationReport(sb, seq, true, true, minmax);
- expected = "<i><br>SeqDesc<br>UNIPROT P30419<br>PDB 3iu1<br>Metal ; Desc<br>Type1 ; Nonpos<br>Variant ; Havana; benign</i>";
+ FeatureColourI fc = new FeatureColour(Color.green, Color.pink, 2, 3);
+ fc.setAttributeName("clinical_significance");
+ fr.setColour("Variant", fc);
+ sar.createSequenceAnnotationReport(sb, seq, true, true, fr);
+ expected = "<i><br>SeqDesc<br>UNIPROT P30419<br>PDB 3iu1<br>Metal ; Desc<br>"
+ + "Type1 ; Nonpos<br>Variant ; Havana; clinical_significance=benign</i>";
assertEquals(expected, sb.toString());
// with showNonPositionalFeatures = false
sb.setLength(0);
- sar.createSequenceAnnotationReport(sb, seq, true, false, minmax);
+ sar.createSequenceAnnotationReport(sb, seq, true, false, fr);
expected = "<i><br>SeqDesc<br>UNIPROT P30419<br>PDB 3iu1</i>";
assertEquals(expected, sb.toString());
FeatureRenderer fr = new FeatureRenderer(av);
List<SequenceFeature> features = new ArrayList<>();
- fr.filterFeaturesForDisplay(features, null); // empty list, does nothing
+ fr.filterFeaturesForDisplay(features); // empty list, does nothing
SequenceI seq = av.getAlignment().getSequenceAt(0);
SequenceFeature sf1 = new SequenceFeature("Cath", "", 6, 8, Float.NaN,
* filter out duplicate (co-located) features
* note: which gets removed is not guaranteed
*/
- fr.filterFeaturesForDisplay(features, new FeatureColour(Color.blue));
+ fr.filterFeaturesForDisplay(features);
assertEquals(features.size(), 3);
assertTrue(features.contains(sf1) || features.contains(sf4));
assertFalse(features.contains(sf1) && features.contains(sf4));
assertTrue(features.contains(sf5));
/*
- * hide group 3 - sf3 is removed, sf2 is retained
- */
- fr.setGroupVisibility("group3", false);
- features = seq.getSequenceFeatures();
- fr.filterFeaturesForDisplay(features, new FeatureColour(Color.blue));
- assertEquals(features.size(), 3);
- assertTrue(features.contains(sf1) || features.contains(sf4));
- assertFalse(features.contains(sf1) && features.contains(sf4));
- assertTrue(features.contains(sf2));
- assertFalse(features.contains(sf3));
- assertTrue(features.contains(sf5));
-
- /*
- * hide group 2, show group 3 - sf2 is removed, sf3 is retained
+ * hide groups 2 and 3 makes no difference to this method
*/
fr.setGroupVisibility("group2", false);
- fr.setGroupVisibility("group3", true);
+ fr.setGroupVisibility("group3", false);
features = seq.getSequenceFeatures();
- fr.filterFeaturesForDisplay(features, null);
+ fr.filterFeaturesForDisplay(features);
assertEquals(features.size(), 3);
assertTrue(features.contains(sf1) || features.contains(sf4));
assertFalse(features.contains(sf1) && features.contains(sf4));
- assertFalse(features.contains(sf2));
- assertTrue(features.contains(sf3));
- assertTrue(features.contains(sf5));
-
- /*
- * no filtering of co-located features with graduated colour scheme
- * filterFeaturesForDisplay does _not_ check colour threshold
- * sf2 is removed as its group is hidden
- */
- features = seq.getSequenceFeatures();
- fr.filterFeaturesForDisplay(features, new FeatureColour(Color.black,
- Color.white, 0f, 1f));
- assertEquals(features.size(), 4);
- assertTrue(features.contains(sf1));
- assertTrue(features.contains(sf3));
- assertTrue(features.contains(sf4));
- assertTrue(features.contains(sf5));
-
- /*
- * co-located features with colour by label
- * should not get filtered
- */
- features = seq.getSequenceFeatures();
- FeatureColour fc = new FeatureColour(Color.black);
- fc.setColourByLabel(true);
- fr.filterFeaturesForDisplay(features, fc);
- assertEquals(features.size(), 4);
- assertTrue(features.contains(sf1));
- assertTrue(features.contains(sf3));
- assertTrue(features.contains(sf4));
+ assertTrue(features.contains(sf2) || features.contains(sf3));
+ assertFalse(features.contains(sf2) && features.contains(sf3));
assertTrue(features.contains(sf5));
}
}
import java.awt.Color;
+import junit.extensions.PA;
+
import org.testng.annotations.BeforeClass;
import org.testng.annotations.Test;
assertTrue(fc1.getColour().equals(Color.RED));
assertFalse(fc1.isGraduatedColour());
assertFalse(fc1.isColourByLabel());
+ assertFalse(fc1.isColourByAttribute());
+ assertNull(fc1.getAttributeName());
/*
* min-max colour
assertTrue(fc1.isGraduatedColour());
assertFalse(fc1.isColourByLabel());
assertTrue(fc1.isAboveThreshold());
+ assertFalse(fc1.isColourByAttribute());
+ assertNull(fc1.getAttributeName());
assertEquals(12f, fc1.getThreshold());
assertEquals(Color.gray, fc1.getMinColour());
assertEquals(Color.black, fc1.getMaxColour());
+ assertEquals(Color.gray, fc1.getNoColour());
+ assertEquals(10f, fc1.getMin());
+ assertEquals(20f, fc1.getMax());
+
+ /*
+ * min-max-noValue colour
+ */
+ fc = new FeatureColour(Color.gray, Color.black, Color.green, 10f, 20f);
+ fc.setAboveThreshold(true);
+ fc.setThreshold(12f);
+ fc1 = new FeatureColour(fc);
+ assertTrue(fc1.isGraduatedColour());
+ assertFalse(fc1.isColourByLabel());
+ assertFalse(fc1.isColourByAttribute());
+ assertNull(fc1.getAttributeName());
+ assertTrue(fc1.isAboveThreshold());
+ assertEquals(12f, fc1.getThreshold());
+ assertEquals(Color.gray, fc1.getMinColour());
+ assertEquals(Color.black, fc1.getMaxColour());
+ assertEquals(Color.green, fc1.getNoColour());
assertEquals(10f, fc1.getMin());
assertEquals(20f, fc1.getMax());
fc1 = new FeatureColour(fc);
assertTrue(fc1.isColourByLabel());
assertFalse(fc1.isGraduatedColour());
+ assertFalse(fc1.isColourByAttribute());
+ assertNull(fc1.getAttributeName());
+
+ /*
+ * colour by attribute (label)
+ */
+ fc = new FeatureColour();
+ fc.setColourByLabel(true);
+ fc.setAttributeName("AF");
+ fc1 = new FeatureColour(fc);
+ assertTrue(fc1.isColourByLabel());
+ assertFalse(fc1.isGraduatedColour());
+ assertTrue(fc1.isColourByAttribute());
+ assertEquals("AF", fc1.getAttributeName());
+
+ /*
+ * colour by attribute (value)
+ */
+ fc = new FeatureColour(Color.gray, Color.black, Color.green, 10f, 20f);
+ fc.setAboveThreshold(true);
+ fc.setThreshold(12f);
+ fc.setAttributeName("AF");
+ fc1 = new FeatureColour(fc);
+ assertTrue(fc1.isGraduatedColour());
+ assertFalse(fc1.isColourByLabel());
+ assertTrue(fc1.isColourByAttribute());
+ assertEquals("AF", fc1.getAttributeName());
+ assertTrue(fc1.isAboveThreshold());
+ assertEquals(12f, fc1.getThreshold());
+ assertEquals(Color.gray, fc1.getMinColour());
+ assertEquals(Color.black, fc1.getMaxColour());
+ assertEquals(Color.green, fc1.getNoColour());
+ assertEquals(10f, fc1.getMin());
+ assertEquals(20f, fc1.getMax());
+ }
+
+ @Test(groups = { "Functional" })
+ public void testCopyConstructor_minMax()
+ {
+ /*
+ * graduated colour
+ */
+ FeatureColour fc = new FeatureColour(Color.BLUE, Color.RED, 1f, 5f);
+ assertTrue(fc.isGraduatedColour());
+ assertFalse(fc.isColourByLabel());
+ assertFalse(fc.isColourByAttribute());
+ assertNull(fc.getAttributeName());
+ assertEquals(1f, fc.getMin());
+ assertEquals(5f, fc.getMax());
+
+ /*
+ * update min-max bounds
+ */
+ FeatureColour fc1 = new FeatureColour(fc, 2f, 6f);
+ assertTrue(fc1.isGraduatedColour());
+ assertFalse(fc1.isColourByLabel());
+ assertFalse(fc1.isColourByAttribute());
+ assertNull(fc1.getAttributeName());
+ assertEquals(2f, fc1.getMin());
+ assertEquals(6f, fc1.getMax());
+ assertFalse((boolean) PA.getValue(fc1, "isHighToLow"));
+
+ /*
+ * update min-max bounds - high to low
+ */
+ fc1 = new FeatureColour(fc, 23f, 16f);
+ assertTrue(fc1.isGraduatedColour());
+ assertFalse(fc1.isColourByLabel());
+ assertFalse(fc1.isColourByAttribute());
+ assertNull(fc1.getAttributeName());
+ assertEquals(23f, fc1.getMin());
+ assertEquals(16f, fc1.getMax());
+ assertTrue((boolean) PA.getValue(fc1, "isHighToLow"));
+
+ /*
+ * colour by label
+ */
+ fc = new FeatureColour(Color.BLUE, Color.RED, 1f, 5f);
+ fc.setColourByLabel(true);
+ assertFalse(fc.isGraduatedColour());
+ assertTrue(fc.isColourByLabel());
+ assertFalse(fc.isColourByAttribute());
+ assertNull(fc.getAttributeName());
+ assertEquals(1f, fc.getMin());
+ assertEquals(5f, fc.getMax());
+
+ /*
+ * update min-max bounds - converts to graduated colour
+ */
+ fc1 = new FeatureColour(fc, 2f, 6f);
+ assertTrue(fc1.isGraduatedColour());
+ assertFalse(fc1.isColourByLabel());
+ assertFalse(fc1.isColourByAttribute());
+ assertNull(fc1.getAttributeName());
+ assertEquals(2f, fc1.getMin());
+ assertEquals(6f, fc1.getMax());
}
@Test(groups = { "Functional" })
@Test(groups = { "Functional" })
public void testGetColor_Graduated()
{
- // graduated colour from score 0 to 100, gray(128, 128, 128) to red(255, 0,
- // 0)
+ /*
+ * graduated colour from
+ * score 0 to 100
+ * gray(128, 128, 128) to red(255, 0, 0)
+ */
FeatureColour fc = new FeatureColour(Color.GRAY, Color.RED, 0f, 100f);
// feature score is 75 which is 3/4 of the way from GRAY to RED
SequenceFeature sf = new SequenceFeature("type", "desc", 0, 20, 75f,
fc = FeatureColour.parseJalviewFeatureColour(descriptor);
assertTrue(fc.isGraduatedColour());
}
+
+ @Test(groups = { "Functional" })
+ public void testGetColor_colourByAttributeText()
+ {
+ FeatureColour fc = new FeatureColour();
+ fc.setColourByLabel(true);
+ fc.setAttributeName("consequence");
+ SequenceFeature sf = new SequenceFeature("type", "desc", 0, 20, 1f,
+ null);
+
+ /*
+ * if feature has no such attribute, use 'no value' colour
+ */
+ assertEquals(FeatureColour.DEFAULT_NO_COLOUR, fc.getColor(sf));
+
+ /*
+ * if feature has attribute, generate colour from value
+ */
+ sf.setValue("consequence", "benign");
+ Color expected = ColorUtils.createColourFromName("benign");
+ assertEquals(expected, fc.getColor(sf));
+ }
+
+ @Test(groups = { "Functional" })
+ public void testGetColor_GraduatedByAttributeValue()
+ {
+ /*
+ * graduated colour based on attribute value for AF
+ * given a min-max range of 0-100
+ */
+ FeatureColour fc = new FeatureColour(new Color(50, 100, 150),
+ new Color(150, 200, 250), Color.yellow, 0f, 100f);
+ String attName = "AF";
+ fc.setAttributeName(attName);
+
+ /*
+ * first case: feature lacks the attribute - use 'no value' colour
+ */
+ SequenceFeature sf = new SequenceFeature("type", "desc", 0, 20, 75f,
+ null);
+ assertEquals(Color.yellow, fc.getColor(sf));
+
+ /*
+ * second case: attribute present but not numeric - treat as if absent
+ */
+ sf.setValue(attName, "twelve");
+ assertEquals(Color.yellow, fc.getColor(sf));
+
+ /*
+ * third case: valid attribute value
+ */
+ sf.setValue(attName, "20.0");
+ Color expected = new Color(70, 120, 170);
+ assertEquals(expected, fc.getColor(sf));
+ }
}
import static org.testng.Assert.assertEquals;
import static org.testng.Assert.assertFalse;
+import static org.testng.Assert.assertSame;
import static org.testng.Assert.assertTrue;
+import java.util.Iterator;
import java.util.function.Function;
import org.testng.annotations.Test;
assertEquals(km1.toString(), "AF < 1.2");
KeyedMatcher km2 = new KeyedMatcher("CLIN_SIG", Condition.NotContains, "path");
- assertEquals(km2.toString(), "CLIN_SIG Does not contain PATH");
+ assertEquals(km2.toString(), "CLIN_SIG Does not contain 'PATH'");
/*
* AND them
assertEquals(kms.toString(), "(AF < 1.2)");
kms.and(km2);
assertEquals(kms.toString(),
- "(AF < 1.2) AND (CLIN_SIG Does not contain PATH)");
+ "(AF < 1.2) AND (CLIN_SIG Does not contain 'PATH')");
/*
* OR them
assertEquals(kms.toString(), "(AF < 1.2)");
kms.or(km2);
assertEquals(kms.toString(),
- "(AF < 1.2) OR (CLIN_SIG Does not contain PATH)");
+ "(AF < 1.2) OR (CLIN_SIG Does not contain 'PATH')");
}
@Test
kms.and(km);
assertFalse(kms.isEmpty());
}
+
+ @Test
+ public void testGetMatchers()
+ {
+ KeyedMatcherSetI kms = new KeyedMatcherSet();
+
+ /*
+ * empty iterable:
+ */
+ Iterator<KeyedMatcherI> iterator = kms.getMatchers().iterator();
+ assertFalse(iterator.hasNext());
+
+ /*
+ * one matcher:
+ */
+ KeyedMatcherI km1 = new KeyedMatcher("AF", Condition.GE, -2F);
+ kms.and(km1);
+ iterator = kms.getMatchers().iterator();
+ assertSame(km1, iterator.next());
+ assertFalse(iterator.hasNext());
+
+ /*
+ * two matchers:
+ */
+ KeyedMatcherI km2 = new KeyedMatcher("AF", Condition.LT, 8F);
+ kms.and(km2);
+ iterator = kms.getMatchers().iterator();
+ assertSame(km1, iterator.next());
+ assertSame(km2, iterator.next());
+ assertFalse(iterator.hasNext());
+ }
}
assertEquals(m.toString(), "LT 1.2E-6");
m = new Matcher(Condition.NotMatches, "ABC");
- assertEquals(m.toString(), "NotMatches ABC");
+ assertEquals(m.toString(), "NotMatches 'ABC'");
m = new Matcher(Condition.Contains, -1.2f);
- assertEquals(m.toString(), "Contains -1.2");
+ assertEquals(m.toString(), "Contains '-1.2'");
}
@Test