milk.visualize.PlotPanel.java Source code

Java tutorial

Introduction

Here is the source code for milk.visualize.PlotPanel.java

Source

/*
 *    This program is free software; you can redistribute it and/or modify
 *    it under the terms of the GNU General Public License as published by
 *    the Free Software Foundation; either version 2 of the License, or
 *    (at your option) any later version.
 *
 *    This program is distributed in the hope that it will be useful,
 *    but WITHOUT ANY WARRANTY; without even the implied warranty of
 *    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *    GNU General Public License for more details.
 *
 *    You should have received a copy of the GNU General Public License
 *    along with this program; if not, write to the Free Software
 *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
 */

/*
 *    PlotPanel.java
 *    Copyright (C) 2001 WEKA, Xin XU
 *
 */

package milk.visualize;

import milk.classifiers.*;
import milk.core.*;

import java.awt.*;
import java.awt.event.*;
import javax.swing.*;
import javax.swing.event.*;

import weka.core.*;
import java.beans.*;
import java.util.*;

/**
 * This class implements a panel that display the distributions of the
 * selected exemplars.  The distributions are derived by summing the
 * small Gaussian on each instance within the exemplar.  Details see
 * Detterich et al's MI paper.
 *
 * @author WEKA
 * @author Xin XU (xx5@cs.waikato.ac.nz)
 * @version $Revision: 1.00 $
 */
public class PlotPanel extends JPanel implements PropertyChangeListener {

    /** precision constant */
    public static int MAX_PRECISION = 10;

    /** Default colour for the axis */
    private Color m_axisColour = Color.black;

    /** Default colour for the plot background */
    private Color m_backgroundColour = Color.white;

    /** The exemplars to be plotted */
    protected Exemplars plotExemplars = null;

    /** The list of the colors used */
    protected FastVector colorList;

    /** The max and min color */
    private double m_minC = Double.NaN, m_maxC = Double.NaN;

    /** Indexes of the attributes to go on the x axis */
    protected Axis x = null;

    /** The std. deviations used in deriving the distributions */
    protected double stdDev = 0.0;

    /** The maximal value in Y axis */
    protected double maxY = 1.0;

    /** Axis padding */
    private final int m_axisPad = 5;

    /** Tick size */
    private final int m_tickSize = 5;

    /** The offsets of the axes once label metrics are calculated */
    private int m_XaxisStart = 0;
    private int m_XaxisEnd = 0;
    private int m_YaxisStart = 0;
    private int m_YaxisEnd = 0;

    /** Whether the distribution curve is up or down */
    protected boolean isUp = true;

    /** Font for labels */
    private Font m_labelFont;
    private FontMetrics m_labelMetrics = null;

    /** Constructor */
    public PlotPanel() {
        setBackground(m_backgroundColour);
        setEnabled(false);
    }

    /**
     * The handler function when property changes are informed.
     * there are lots of properties can be listened by this
     * class: the exemplars to be plotted, the colors
     * from the ClassPanel, x axis value and index, and std. dev.
     * Only all these parameters are set can the distribution be
     * plotted
     *
     * @param pce the property change event
     */
    public void propertyChange(PropertyChangeEvent pce) {
        try {
            // From SelectPanel
            if (pce.getPropertyName().equals("addedExs"))
                setPlotExemplars((Exemplars) pce.getNewValue());
        } catch (Exception e) {
            JOptionPane.showMessageDialog(this, e.getMessage(), "Should never happen!", JOptionPane.ERROR_MESSAGE);
        }
        // From DistributionPanel
        if (pce.getPropertyName().equals("colorList"))
            setColours((FastVector) pce.getNewValue());

        if (pce.getPropertyName().equals("maxC"))
            setMaxC(((Double) pce.getNewValue()).doubleValue());

        if (pce.getPropertyName().equals("minC"))
            setMinC(((Double) pce.getNewValue()).doubleValue());

        if (pce.getPropertyName().equals("XAxis"))
            setXValue((Axis) (pce.getNewValue()));

        if (pce.getPropertyName().equals("SD"))
            setStdDev(((Double) pce.getNewValue()).doubleValue());

        check();
        repaint();
    }

    public void setStdDev(double sd) {
        stdDev = sd;
        maxY = 1.0 / (stdDev * Math.sqrt(2.0 * Math.PI));
    }

    private void check() {
        if (x != null) {
            if (plotExemplars != null) {
                Attribute clas = plotExemplars.classAttribute();
                if (clas.isNominal() && (colorList != null))
                    setEnabled(true);
                else if ((!Double.isNaN(m_maxC)) && (!Double.isNaN(m_minC)))
                    setEnabled(true);
            }
        }
    }

    /**
     * Set a list of colours to use when colouring points according
     * to class values or cluster numbers
     * @param cols the list of colours to use
     */
    public void setColours(FastVector cols) {
        colorList = cols;
    }

    public void setMaxC(double maxc) {
        m_maxC = maxc;
    }

    public void setMinC(double minc) {
        m_minC = minc;
    }

    /**
     * Set the index of the attribute to go on the x axis
     * @param x the index of the attribute to use on the x axis
     */
    public void setXValue(Axis anx) {
        x = anx;
    }

    /** 
     * Return the current max value of the attribute plotted on the x axis
     * @return the x value
     */
    public Axis getXValue() {
        return x;
    }

    /** 
     * Return the current max value of the attribute plotted on the y axis,
     * i.e. 1/[sqrt(2*PI) * std. dev.]
     * @return the max y value
     */
    public double getMaxY() {
        return maxY;
    }

    /**
     * Sets the exemplars to be plotted
     * @param added the exemplars
     * @exception exception Exception if exemplars could not be set
     */
    public void setPlotExemplars(Exemplars added) throws Exception {
        if (added != null)
            plotExemplars = new Exemplars(added);
        else {
            setEnabled(false);
            plotExemplars = null;
        }
    }

    /**
     * Set up fonts and font metrics
     * @param gx the graphics context
     */
    private void setFonts(Graphics gx) {
        if (m_labelMetrics == null) {
            m_labelFont = new Font("Monospaced", Font.PLAIN, 12);
            m_labelMetrics = gx.getFontMetrics(m_labelFont);
        }
        gx.setFont(m_labelFont);
    }

    /**
     * convert a Panel x coordinate to a raw x value.
     * @param scx The Panel x coordinate
     * @return A raw x value.
     */
    public double convertToAttribX(double scx) {
        double temp = m_XaxisEnd - m_XaxisStart;
        double temp2 = ((scx - m_XaxisStart) * (x.maxValue - x.minValue)) / temp + x.minValue;

        return temp2;
    }

    /**
     * convert a Panel y coordinate to a raw y value.
     * @param scy The Panel y coordinate
     * @return A raw y value.
     */
    public double convertToAttribY(double scy) {
        double temp = m_YaxisEnd - m_YaxisStart;
        double temp2 = ((m_YaxisEnd - scy) * maxY) / temp;

        return temp2;
    }

    /**
     * Convert an raw x value to Panel x coordinate.
     * @param xval the raw x value
     * @return an x value for plotting in the panel.
     */
    public double convertToPanelX(double xval) {
        double temp = (xval - x.minValue) / (x.maxValue - x.minValue);
        double temp2 = temp * (m_XaxisEnd - m_XaxisStart);

        temp2 = temp2 + m_XaxisStart;

        return temp2;
    }

    /**
     * Convert an raw y value to Panel y coordinate.
     * @param yval the raw y value
     * @return an y value for plotting in the panel.
     */
    public double convertToPanelY(double yval) {
        double temp = yval / maxY;
        double temp2 = temp * (m_YaxisEnd - m_YaxisStart);

        temp2 = m_YaxisEnd - temp2;

        return temp2;
    }

    /**
     * Draws the distribution
     * @param gx the graphics context
     */
    private void paintData(Graphics gx) {
        if ((plotExemplars == null) || (x == null))
            return;

        setFonts(gx);
        int w = this.getWidth();
        boolean[] xlabels = new boolean[w];

        Attribute classAtt = plotExemplars.classAttribute(), id = plotExemplars.idAttribute();
        int hf = m_labelMetrics.getAscent();

        for (int i = 0; i < plotExemplars.numExemplars(); i++) {
            Exemplar ex = plotExemplars.exemplar(i);
            if (classAtt.isNominal())
                gx.setColor((Color) colorList.elementAt((int) ex.classValue()));
            else {
                double r = (ex.classValue() - m_minC) / (m_maxC - m_minC);
                r = (r * 240) + 15;
                gx.setColor(new Color((int) r, 150, (int) (255 - r)));
            }

            double preY = 0;
            for (int j = 0; j < ex.getInstances().numInstances(); j++) {
                Instance ins = ex.getInstances().instance(j);
                double xValue = convertToAttribX(m_XaxisStart);
                double tmp = -(xValue - ins.value(x.index)) * (xValue - ins.value(x.index))
                        / (2.0 * stdDev * stdDev);
                preY += Math.exp(tmp) * ins.weight();
            }
            preY /= ex.getInstances().sumOfWeights();
            preY *= maxY;

            for (int k = m_XaxisStart + 1; k < m_XaxisEnd; k++) {
                double currY = 0;
                for (int l = 0; l < ex.getInstances().numInstances(); l++) {
                    Instance ins = ex.getInstances().instance(l);
                    double xValue = convertToAttribX(k);
                    double tmp = -(xValue - ins.value(x.index)) * (xValue - ins.value(x.index))
                            / (2.0 * stdDev * stdDev);
                    currY += Math.exp(tmp) * ins.weight();
                }
                currY /= ex.getInstances().sumOfWeights();
                currY *= maxY;

                // Draw the distribution         
                int plotPreY = (int) convertToPanelY(preY);
                int plotCurrY = (int) convertToPanelY(currY);
                gx.drawLine(k - 1, plotPreY, k, plotCurrY);

                // If peak or valley appears, specify the x value
                if (isUp && (preY > currY)) {
                    Font old = gx.getFont();
                    gx.setFont(new Font("Monospaced", Font.PLAIN, 10));
                    String idvalue = Integer.toString((int) ex.idValue());
                    gx.drawString(idvalue, k - 1 - m_labelMetrics.stringWidth(idvalue) / 2, plotCurrY);
                    xlabels[k - 1] = true;
                    gx.setFont(old);
                }
                isUp = (currY >= preY);
                preY = currY;
            }
        }

        // Draw the number labels on x axis where various peaks gather
        gx.setColor(m_axisColour);
        int start = 0, end = 0;
        while (start < w) {
            if (xlabels[start]) {
                end = start;
                int falseCount = 0;
                while (end < w) {
                    while (xlabels[end++])
                        ; // Find the first false from start
                    int m = end;
                    // Count the number of falses
                    for (falseCount = 0; (m < xlabels.length) && (!xlabels[m]); m++, falseCount++)
                        ;

                    if ((falseCount < 28) && (m < xlabels.length))
                        end = m;
                    else
                        break;
                }
                if (!xlabels[end])
                    --end;

                int avg = (start + end) / 2;
                double xValue = convertToAttribX(avg);
                String stringX = Utils.doubleToString(xValue, 1);
                Font old = gx.getFont();
                gx.setFont(new Font("Monospaced", Font.PLAIN, 10));
                gx.drawString(stringX, avg - m_labelMetrics.stringWidth(stringX) / 2, m_YaxisEnd + hf + m_tickSize);
                gx.drawLine(avg, m_YaxisEnd, avg, m_YaxisEnd + m_tickSize);
                gx.setFont(old);
                start = end;
            } else
                ++start;
        }
    }

    /**
     * Draws the axis 
     * @param gx the graphics context
     */
    private void paintAxis(Graphics gx) {
        setFonts(gx);
        gx.setColor(m_axisColour);
        int mxs = m_XaxisStart;
        int mxe = m_XaxisEnd;
        int mys = m_YaxisStart;
        int mye = m_YaxisEnd;

        int h = this.getHeight();
        int w = this.getWidth();
        int hf = m_labelMetrics.getAscent();
        int mswx = 0;
        int mswy = 0;

        int precisionXmax = 1;
        int precisionXmin = 1;
        int precisionYmax = 1;

        String minStringX = "", maxStringX = "";
        int whole, nondecimal;
        double decimal;
        if (x != null) {
            whole = (int) Math.abs(x.maxValue);
            decimal = Math.abs(x.maxValue) - whole;
            nondecimal = (whole > 0) ? (int) (Math.log(whole) / Math.log(10)) : 1;

            precisionXmax = (decimal > 0) ? (int) Math.abs(((Math.log(Math.abs(x.maxValue)) / Math.log(10)))) + 2
                    : 1;
            if (precisionXmax > MAX_PRECISION) {
                precisionXmax = 1;
            }

            maxStringX = Utils.doubleToString(x.maxValue, nondecimal + 1 + precisionXmax, precisionXmax);

            whole = (int) Math.abs(x.minValue);
            decimal = Math.abs(x.minValue) - whole;
            nondecimal = (whole > 0) ? (int) (Math.log(whole) / Math.log(10)) : 1;
            precisionXmin = (decimal > 0) ? (int) Math.abs(((Math.log(Math.abs(x.minValue)) / Math.log(10)))) + 2
                    : 1;
            if (precisionXmin > MAX_PRECISION) {
                precisionXmin = 1;
            }

            minStringX = Utils.doubleToString(x.minValue, nondecimal + 1 + precisionXmin, precisionXmin);

            mswx = m_labelMetrics.stringWidth(maxStringX);
        }

        whole = (int) Math.abs(maxY);
        decimal = Math.abs(maxY) - whole;
        nondecimal = (whole > 0) ? (int) (Math.log(whole) / Math.log(10)) : 1;
        precisionYmax = (decimal > 0) ? (int) Math.abs(((Math.log(Math.abs(maxY)) / Math.log(10)))) + 2 : 1;
        if (precisionYmax > MAX_PRECISION) {
            precisionYmax = 1;
        }

        String stringY = Utils.doubleToString(maxY, nondecimal + 1 + precisionYmax, precisionYmax);

        mswy = m_labelMetrics.stringWidth(stringY);

        m_YaxisStart = m_axisPad;
        m_XaxisStart = 0 + m_axisPad + m_tickSize + mswy;

        m_XaxisEnd = w - m_axisPad - (mswx / 2);

        m_YaxisEnd = h - m_axisPad - (2 * hf) - m_tickSize;

        if (x != null) {
            if (w > (2 * mswx)) {
                gx.drawString(maxStringX, m_XaxisEnd - (mswx / 2), m_YaxisEnd + hf + m_tickSize);

                mswx = m_labelMetrics.stringWidth(minStringX);
                gx.drawString(minStringX, (m_XaxisStart - (mswx / 2)), m_YaxisEnd + hf + m_tickSize);
            }
        }

        // draw the y axis
        if (h > (2 * hf)) {
            gx.drawString(stringY, m_XaxisStart - mswy - m_tickSize, m_YaxisStart + (hf));

            gx.drawString("0", (m_XaxisStart - mswy - m_tickSize), m_YaxisEnd);
        }

        gx.drawLine(m_XaxisStart, m_YaxisStart, m_XaxisStart, m_YaxisEnd);
        gx.drawLine(m_XaxisStart, m_YaxisEnd, m_XaxisEnd, m_YaxisEnd);
    }

    /**
     * Renders this component
     * @param gx the graphics context
     */
    public void paintComponent(Graphics gx) {
        super.paintComponent(gx);
        if (!isEnabled())
            return;

        paintAxis(gx);
        paintData(gx);

    }
}