From: gmungoc Date: Tue, 7 Nov 2017 11:48:16 +0000 (+0000) Subject: JAL-2069 add 'by attribute' options to graduated feature colour X-Git-Tag: Release_2_11_0~145 X-Git-Url: http://source.jalview.org/gitweb/?p=jalview.git;a=commitdiff_plain;h=9feb54a4d32293b760afcdc29672fb6a880c6cbb JAL-2069 add 'by attribute' options to graduated feature colour --- diff --git a/resources/lang/Messages.properties b/resources/lang/Messages.properties index 851585a..35ead5e 100644 --- a/resources/lang/Messages.properties +++ b/resources/lang/Messages.properties @@ -283,7 +283,6 @@ label.sequence = Sequence 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 @@ -532,7 +531,7 @@ label.threshold_feature_above_threshold = Above Threshold 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} @@ -1335,8 +1334,18 @@ label.matchCondition_le = <= 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 diff --git a/src/jalview/datamodel/features/FeatureAttributes.java b/src/jalview/datamodel/features/FeatureAttributes.java index ed65750..3dc4f19 100644 --- a/src/jalview/datamodel/features/FeatureAttributes.java +++ b/src/jalview/datamodel/features/FeatureAttributes.java @@ -34,6 +34,11 @@ public class FeatureAttributes */ 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 @@ -43,27 +48,19 @@ public class FeatureAttributes */ void addInstance(String desc, String value) { - if (desc != null) + addDescription(desc); + + if (value != null) { - if (description == null) - { - description = new ArrayList<>(); - } - if (!description.contains(desc)) + try { - description.add(desc); - } - if (value != null) + float f = Float.valueOf(value); + min = Float.min(min, f); + max = Float.max(max, f); + hasValue = true; + } catch (NumberFormatException e) { - try - { - float f = Float.valueOf(value); - min = Float.min(min, f); - max = Float.max(max, f); - } catch (NumberFormatException e) - { - // ok, wasn't a number - } + // ok, wasn't a number, ignore for min-max purposes } } } @@ -80,6 +77,27 @@ public class FeatureAttributes } 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); + } + } + } } /** @@ -188,4 +206,59 @@ public class FeatureAttributes } 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 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 atts = attributes.get(featureType); + if (atts == null) + { + atts = new TreeMap( + 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); + } } diff --git a/src/jalview/gui/FeatureColourChooser.java b/src/jalview/gui/FeatureColourChooser.java index 89b64a7..3fc3116 100644 --- a/src/jalview/gui/FeatureColourChooser.java +++ b/src/jalview/gui/FeatureColourChooser.java @@ -22,27 +22,35 @@ package jalview.gui; 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; @@ -51,7 +59,8 @@ import javax.swing.event.ChangeListener; public class FeatureColourChooser extends JalviewDialog { - // FeatureSettings fs; + private static final int MAX_TOOLTIP_LENGTH = 50; + private FeatureRenderer fr; private FeatureColourI cs; @@ -62,11 +71,11 @@ public class FeatureColourChooser extends JalviewDialog 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; @@ -74,27 +83,50 @@ public class FeatureColourChooser extends JalviewDialog private JPanel maxColour = new JPanel(); + private JPanel noColour = new JPanel(); + private JComboBox 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 textAttributeCombo; + + /* + * choice of attribute (if any) for 'colour by value' + */ + private JComboBox valueAttributeCombo; + /** * Constructor * @@ -123,7 +155,7 @@ public class FeatureColourChooser extends JalviewDialog 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() { @@ -179,7 +211,10 @@ public class FeatureColourChooser extends JalviewDialog } 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) { @@ -187,10 +222,11 @@ public class FeatureColourChooser extends JalviewDialog } // 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 @@ -198,10 +234,46 @@ public class FeatureColourChooser extends JalviewDialog 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 @@ -220,39 +292,193 @@ public class FeatureColourChooser extends JalviewDialog 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 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 @@ -288,25 +514,65 @@ public class FeatureColourChooser extends JalviewDialog 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()); } /** @@ -324,76 +590,41 @@ public class FeatureColourChooser extends JalviewDialog } /** - * 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 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; } /** @@ -433,6 +664,24 @@ public class FeatureColourChooser extends JalviewDialog } /** + * 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 @@ -462,6 +711,9 @@ public class FeatureColourChooser extends JalviewDialog slider.setEnabled(true); thresholdValue.setEnabled(true); + /* + * make the feature colour + */ FeatureColourI acg; if (cs.isColourByLabel()) { @@ -470,8 +722,23 @@ public class FeatureColourChooser extends JalviewDialog 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) { @@ -504,7 +771,7 @@ public class FeatureColourChooser extends JalviewDialog slider.setMajorTickSpacing((int) (range / 10f)); slider.setEnabled(true); thresholdValue.setEnabled(true); - thresholdIsMin.setEnabled(!colourByLabel.isSelected()); + thresholdIsMin.setEnabled(!byDescription.isSelected()); adjusting = false; } @@ -526,27 +793,37 @@ public class FeatureColourChooser extends JalviewDialog { 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); @@ -653,4 +930,82 @@ public class FeatureColourChooser extends JalviewDialog 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 populateAttributesDropdown( + String featureType, List attNames, + boolean withNumericRange) + { + List validAtts = new ArrayList<>(); + List 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 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); + } + } + } diff --git a/src/jalview/gui/FeatureSettings.java b/src/jalview/gui/FeatureSettings.java index 0928ec1..3be597c 100644 --- a/src/jalview/gui/FeatureSettings.java +++ b/src/jalview/gui/FeatureSettings.java @@ -84,6 +84,7 @@ import java.util.Vector; 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; @@ -122,6 +123,8 @@ public class FeatureSettings extends JPanel private static final int MIN_HEIGHT = 400; + private static final int MAX_TOOLTIP_LENGTH = 50; + DasSourceBrowser dassourceBrowser; DasSequenceFeatureFetcher dasFeatureFetcher; @@ -414,84 +417,63 @@ public class FeatureSettings extends JPanel }); 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() @@ -1379,7 +1361,8 @@ public class FeatureSettings extends JPanel /* * 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"))); @@ -1414,7 +1397,9 @@ public class FeatureSettings extends JPanel /* * 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); /* @@ -1471,7 +1456,7 @@ public class FeatureSettings extends JPanel } if (!found) { - filteredFeatureChoice + filteredFeatureChoice // todo i18n .addItem("No filterable feature attributes known"); } @@ -1491,14 +1476,12 @@ public class FeatureSettings extends JPanel * 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 attNames = FeatureAttributes.getInstance().getAttributes( selectedType); @@ -1514,11 +1497,7 @@ public class FeatureSettings extends JPanel { orFilters.setSelected(true); } - Iterator matchers = featureFilters.getMatchers(); - while (matchers.hasNext()) - { - filters.add(matchers.next()); - } + featureFilters.getMatchers().forEach(matcher -> filters.add(matcher)); } /* @@ -1530,16 +1509,16 @@ public class FeatureSettings extends JPanel /* * 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(); @@ -1574,7 +1553,13 @@ public class FeatureSettings extends JPanel /* * inputs for attribute, condition, pattern */ - final JComboBox 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 attCombo = populateAttributesDropdown( + featureType, attNames); JComboBox condCombo = new JComboBox<>(); JTextField patternField = new JTextField(8); @@ -1605,12 +1590,6 @@ public class FeatureSettings extends JPanel } }; - /* - * drop-down choice of attribute, with description as a tooltip - * if we can obtain it - */ - String featureType = (String) filteredFeatureChoice.getSelectedItem(); - populateAttributesDropdown(attCombo, featureType, attNames); if ("".equals(attribute)) { attCombo.setSelectedItem(null); @@ -1681,57 +1660,32 @@ public class FeatureSettings extends JPanel * Where metadata is available with a description for an attribute, that is * added as a tooltip. * - * @param attCombo * @param featureType * @param attNames */ - protected void populateAttributesDropdown( - final JComboBox attCombo, String featureType, - List attNames) + protected JComboBox populateAttributesDropdown( + String featureType, List attNames) { - final ComboBoxTooltipRenderer renderer = new ComboBoxTooltipRenderer(); - attCombo.setRenderer(renderer); - List tips = new ArrayList(); - if (attNames.isEmpty()) - { - attCombo.addItem("---"); - attCombo.setToolTipText(MessageManager - .getString("label.no_attributes_known")); - } - else + List tooltips = new ArrayList<>(); + FeatureAttributes fa = FeatureAttributes.getInstance(); + for (String attName : attNames) { - attCombo.setToolTipText(""); - FeatureAttributes fs = FeatureAttributes.getInstance(); - for (String attName : attNames) + String desc = fa.getDescription(featureType, attName); + if (desc != null && desc.length() > MAX_TOOLTIP_LENGTH) { - attCombo.addItem(attName); - String desc = fs.getDescription(featureType, attName); - tips.add(desc == null ? "" : desc); + desc = desc.substring(0, MAX_TOOLTIP_LENGTH) + "..."; } + tooltips.add(desc == null ? "" : desc); } - renderer.setTooltips(tips); - final MouseAdapter mouseListener = new MouseAdapter() - { - @Override - public void mouseEntered(MouseEvent e) - { - int j = attCombo.getSelectedIndex(); - if (j > -1) - { - attCombo.setToolTipText(tips.get(j)); - } - } - @Override - public void mouseExited(MouseEvent e) - { - attCombo.setToolTipText(null); - } - }; - for (Component c : attCombo.getComponents()) + JComboBox attCombo = JvSwingUtils.buildComboWithTooltips( + attNames, tooltips); + if (attNames.isEmpty()) { - c.addMouseListener(mouseListener); + attCombo.setToolTipText(MessageManager + .getString("label.no_attributes")); } + return attCombo; } /** @@ -2108,12 +2062,7 @@ public class FeatureSettings extends JPanel 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()) @@ -2121,14 +2070,12 @@ public class FeatureSettings extends JPanel 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) { @@ -2179,28 +2126,43 @@ public class FeatureSettings extends JPanel 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 @@ -2216,16 +2178,17 @@ public class FeatureSettings extends JPanel // + ", " + 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()); } } } @@ -2347,7 +2310,8 @@ class ColorEditor extends AbstractCellEditor 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 } diff --git a/src/jalview/viewmodel/seqfeatures/FeatureRendererModel.java b/src/jalview/viewmodel/seqfeatures/FeatureRendererModel.java index 3a97067..c2f5bb7 100644 --- a/src/jalview/viewmodel/seqfeatures/FeatureRendererModel.java +++ b/src/jalview/viewmodel/seqfeatures/FeatureRendererModel.java @@ -490,7 +490,8 @@ public abstract class FeatureRendererModel 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]); } @@ -520,7 +521,8 @@ public abstract class FeatureRendererModel 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]); }