In the KNIME framework the technique known as linking and
brushing
is called HiLiting. This means that whenever a datapoint is
hilited in one view it is immediately also hilited in all other views
displaying this data point. If we had a view that displayed the
datapoints directly we would have to implement the HiLiteListener
interface to be informed about any change in the hiliting. The
HiLiteListener interface has three methods:
/** * Invoked when some item(s) were hilit. * * @param event contains a list of row keys that were hilit */ void hiLite(final KeyEvent event); /** * Invoked when some item(s) were unhilit. * * @param event contains a list of row keys that were unhilit */ void unHiLite(final KeyEvent event); /** * Invoked, when everything (all rows) are unhilit. */ void unHiLiteAll();
But since we have an aggregated view of the datapoints it does not make much sense to implement the HiLiteListener interface. We would rather hilite a bin and see all the datapoints in that bin hilited in other views. In the following we explain how this is implemented. First of all we have to prepare our NumericBin to know when it has been selected and if it is hilited. Therefore we simply introduce two flags to indicate the status:
/** * @param isHilite sets the hilite status of this bin. */ public void setHilited(final boolean isHilite) { m_isHilite = isHilite; } /** * * @return true if this bin contains hilited keys, false otherwise. */ public boolean isHilited() { return m_isHilite; } /** * * @return true if this bin is selected. false otherwise. */ public boolean isSelected() { return m_isSelected; } /** * * @param selected true, if the bin is selected, false otherwise. */ public void setSelected(final boolean selected) { m_isSelected = selected; }
The next step is to listen to the mouse events to be informed about whether a bin is selected or not. Then the bin has to know its graphical representation, i.e. the painted rectangle (otherwise we cannot know if it is clicked or not):
/** * * @return the graphical representation as a rectangle. */ public Rectangle getViewRepresentation() { return m_viewRepresentation; } /** * The graphical representation can only be calculated outside with the * knowledge of the number of bins, the maximal and minimal size * and the available width and height. This is done in the * {@link NumericBinnerViewPanel#paint(java.awt.Graphics)} * * @param rectangle the graphical representation */ public void setViewRepresentation(final Rectangle rectangle) { m_viewRepresentation = rectangle; }
In order to listen to mouse events we have to add a MouseListener in the NodeView's constructor to the drawing component. The selected bins are stored in a local datastructure m_selected:
... m_selected = new HashSet<NumericBin>(); m_panel.addMouseListener(new MouseAdapter() { /** * @see java.awt.event.MouseAdapter#mouseReleased( * java.awt.event.MouseEvent) */ @Override public void mouseReleased(final MouseEvent e) { if (!e.isControlDown()) { m_selected.clear(); for (NumericBin bin : m_panel.getBins()) { bin.setSelected(false); } } for (NumericBin bin : m_panel.getBins()) { if (bin.getViewRepresentation() != null && bin.getViewRepresentation().contains( e.getX(), e.getY())){ bin.setSelected(true); m_selected.add(bin); break; } } ...
So far we are able to select one or more bins. If we want to hilite them we have to add a menu to the NodeView, to enable us to hilite or unhilite the selected bins, or clear the hiliting.
// create the hilite menu // the HiliteHandler provides standard names m_hilite = new JMenuItem(HiLiteHandler.HILITE_SELECTED); m_hilite.setEnabled(false); m_hilite.addActionListener(new ActionListener() { /** * @see java.awt.event.ActionListener#actionPerformed( * java.awt.event.ActionEvent) */ public void actionPerformed(final ActionEvent e) { Set<DataCell> toBeHilited = new HashSet<DataCell>(); for (NumericBin bin : m_selected) { // store all row ids from the selected bin toBeHilited.addAll(bin.getContainedRowIds()); // set the bin hilited bin.setHilited(true); // count the number of hilited bins for a // correct menu display (see below) m_numberOfHilitedBins++; } // now get the hilite handler and hilite the rows getNodeModel().getInHiLiteHandler( NumericBinnerNodeModel.IN_PORT).fireHiLiteEvent(toBeHilited); // and repaint to have the hilited bins displayed correctly m_panel.repaint(); } }); m_unhilite = new JMenuItem(HiLiteHandler.UNHILITE_SELECTED); m_unhilite.setEnabled(false); m_unhilite.addActionListener(new ActionListener() { /** * @see java.awt.event.ActionListener#actionPerformed( * java.awt.event.ActionEvent) */ public void actionPerformed(final ActionEvent e) { Set<DataCell> toBeUnhilited = new HashSet<DataCell>(); for (NumericBin bin : m_selected) { // store all row ids that should be unhilited toBeUnhilited.addAll(bin.getContainedRowIds()); // unhilite the bin bin.setHilited(false); // decrease the number of hilited bins m_numberOfHilitedBins--; } // get the hilite handler and unhilite the rows getNodeModel().getInHiLiteHandler( NumericBinnerNodeModel.IN_PORT).fireUnHiLiteEvent(toBeUnhilited); // repaint to have the bins displayed correctly m_panel.repaint(); } }); JMenuItem clear = new JMenuItem(HiLiteHandler.CLEAR_HILITE); clear.addActionListener(new ActionListener() { /** * @see java.awt.event.ActionListener#actionPerformed( * java.awt.event.ActionEvent) */ public void actionPerformed(final ActionEvent e) { // get the hilite handler and unhilite all getNodeModel().getInHiLiteHandler( NumericBinnerNodeModel.IN_PORT).fireClearHiLiteEvent(); // unhilite all bins for (NumericBin bin : m_panel.getBins()) { bin.setHilited(false); } // no bin is hilited anymore m_numberOfHilitedBins = 0; // repaint to display the bins correctly m_panel.repaint(); } }); // create the menu and all the menu items to it JMenu menu = new JMenu(HiLiteHandler.HILITE); menu.add(m_hilite); menu.add(m_unhilite); menu.add(clear); // get the JMenu bar of the NodeView and add this menu to it getJMenuBar().add(menu); ...
The HiLiteHandler provides standard names for the menu items. The getJMenuBar method returns the MenuBar of the NodeView to which the additional menu can be added. To further improve our small human computer interface we can enable and disable the menu items dependent on the current selection and hilite status, i.e. the hilite menu entry should only be enabled when some bins are selected. And accourdingly should the unhilite menu entry only be enabled when some bins are selected and hilited. We add this functionality to the MouseListener. (By the way this is the reason, why the two menu items are local fields.)
public void mouseReleased(final MouseEvent e) { ... // update the hilite menu if (m_selected.size() > 0) { m_hilite.setEnabled(true); } else { m_hilite.setEnabled(false); } if (m_numberOfHilitedBins > 0 && m_selected.size() > 0) { m_unhilite.setEnabled(true); } else { m_unhilite.setEnabled(false); } m_panel.repaint(); } });
So far we are able to select and hilite (unhilite) the bins and the contained rows. But if you run the code implemented so far you immediately encounter the frustrating fact, that in our view you cannot distinguish between selected, hilited and normal bins. Thus, we have to add this to the paint method of the drawing component, the NumericBinnerViewPanel (it also shows how the graphical rectangle of the bins is updated in every paint):
... Rectangle rect = new Rectangle(x, height - binHeight, binWidth, binHeight); m_bins[i].setViewRepresentation(rect); // draw a border in white to make the bins distinguishable Color color = Color.BLACK; if (m_bins[i].isHilited()) { color = ColorAttr.HILITE; } if (m_bins[i].isSelected()) { color = ColorAttr.SELECTED; } if (m_bins[i].isHilited() && m_bins[i].isSelected()) { color = ColorAttr.SELECTED_HILITE; } Graphics2D g2 = (Graphics2D)g; g2.setColor(color); g2.fillRect(rect.x+2, rect.y+2, rect.width-2, rect.height-2); g2.setColor(Color.WHITE); g2.setStroke(new BasicStroke(2)); g2.drawRect(rect.x, rect.y, rect.width, rect.height); ...
Now the view looks good and is correctly displayed if a bin is selected, hilited, both, or none. We use the KNIME standard colors defined in the ColorAttr to have a uniform coloring in all views.