Implement the NodeView

In this section a NodeView for our node is implemented. In order to display information about the work of this node we display the bins in a histogram, where the height of each bin indicates the number of rows in this bin.

Internal Representation:

A model is required to represent the outcome of our algorithm. Obviously a model representing a bin is appropriate. A bin contains a number of rows and is graphically represented by a rectangle. Therefore, we create this data structure.

	public class NumericBin {
    
    private final Set m_containedRowIds;

    public NumericBin() {
        m_containedRowIds = new HashSet();
    }
    
    /**
     * Adds another row to this bin.
     * @param rowId the row to add to this bin.
     */
    public void addRowToBin(final DataCell rowId) {
        m_containedRowIds.add(rowId);
    }
    
    /**
     * 
     * @return the number of rows in this bin.
     */
    public int getSize() {
        return m_containedRowIds.size();
    }

To obtain the bins filled with the referring row IDs, an array with empty bins must be passed to the NumericBinnerCellFactory, which adds the row to the referring bin in the getCell method:

	/**
     * @see de.unikn.knime.core.data.container.SingleCellFactory#getCell(
     * de.unikn.knime.core.data.DataRow)
     */
    @Override
    public DataCell getCell(DataRow row) {
        DataCell currCell = row.getCell(m_colIndex);
		...
        int binNr = 0;
        for (Double intervalBound : m_intervalUpperBounds) {
            if (currValue <= intervalBound) {
                m_bins[binNr].addRowToBin(row.getKey().getId());
                return new IntCell(binNr);
            }
            binNr++;
        }
		...

Drawing Component:

Before we can start to implement the NodeView we have to implement a component that actually draws our bins. For this purpose we create our own JPanel using a quite simple paint method:

	public class NumericBinnerViewPanel extends JPanel 
...
    /**
     * @see javax.swing.JComponent#paint(java.awt.Graphics)
     */
    @Override
    public void paint(Graphics g) {
        super.paint(g);
        if (m_bins != null && m_bins.length > 0) {
            int maxNr = 0;
            // determine the largest bin
            for (int i = 0; i < m_bins.length; i++) {
                maxNr = Math.max(m_bins[i].getSize(), maxNr);
            }
            // if no size information available (creation) set default size
            int width = getWidth();
            if (width == 0) {
                width = SIZE;
            }
            int height = getHeight();
            if (height == 0) {
                height = SIZE;
            }
            // calculate the bin width
            int binWidth = width / m_bins.length;
            for (int i = 0; i < m_bins.length; i++) {
                // the left side of the rectangle
                int x = i * binWidth;
                // the height of the bin
                int binHeight = height;
                // the larger the bin the higher the rect
                double sizeFactor = ((double)(maxNr - m_bins[i].getSize())
                        / (double)maxNr); 
                // since y-axis starts on top subtract 
                binHeight -= sizeFactor * height;
                Rectangle rect = new Rectangle(x, height - binHeight, binWidth, 
                        binHeight);
                m_bins[i].setViewRepresentation(rect);
                // draw the bin in black
                g.setColor(Color.BLACK);
                g.fillRect(rect.x, rect.y, rect.width, rect.height);
                // draw a border in white to make the bins distinguishable
                g.setColor(Color.WHITE);
                g.drawRect(rect.x, rect.y, rect.width, rect.height);
            }
        }
    }

NodeView:

Implementation of the NodeView is very simple. In the constructor we create the drawing component (our NumericBinnerViewPanel) using the bins retrieved from the model. We set it as our view content via the setComponent method:

	public class NumericBinnerNodeView extends NodeView {

    // panel which actually paints the bins
    private NumericBinnerViewPanel m_panel;
    
    /**
     * Creates a new view.
     * 
     * @param nodeModel the model class: {@link NumericBinnerNodeModel}
     */
    protected NumericBinnerNodeView(final NodeModel nodeModel) {
        super(nodeModel);
        // get the bins
        NumericBin[] bins = ((NumericBinnerNodeModel)getNodeModel())
        	.getBinRepresentations();
        if (bins != null && bins.length >0) {
            // create the panel that draws the bins
            m_panel = new NumericBinnerViewPanel(bins);
        } 
        // sets the view content in the node view
        setComponent(m_panel);
    }

Note that the panel might be null. If this is the case, the view displays a message that no data is available by default. Another situation might be that the view is open and the model changes (due to a different input, or a different configuration). Then the view has to be updated. This is realized by the modelChanged method in the NodeView:

	
    protected void modelChanged() {
        // if the model had changed get the new bins
        NumericBin[] bins = ((NumericBinnerNodeModel)getNodeModel())
        .getBinRepresentations();
        if (bins != null && bins.length > 0 && m_panel != null) {
            // and paint the bins
            ((NumericBinnerViewPanel)m_panel).updateView(bins);
        }
    }

The NumericBinnerViewPanel also needs an update method, as follows:

	    /**
     * If the view is updated the new bins are set and then painted.
     * 
     * @param bins the new bins to display.
     */
    public void updateView(final NumericBin[] bins) {
        m_bins = bins;
        repaint();
    }