HiLiting

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();
		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 toBeHilited = new HashSet();
                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 toBeUnhilited = new HashSet();
                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.