weka.gui.AttributeVisualizationPanel.java Source code

Java tutorial

Introduction

Here is the source code for weka.gui.AttributeVisualizationPanel.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 3 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, see <http://www.gnu.org/licenses/>.
 */

/*
 *    AttributeVisualizationPanel.java
 *    Copyright (C) 2003-2012 University of Waikato, Hamilton, New Zealand
 *
 */

package weka.gui;

import weka.core.Attribute;
import weka.core.AttributeStats;
import weka.core.Instances;
import weka.core.SparseInstance;
import weka.core.Utils;
import weka.gui.visualize.PrintableComponent;
import weka.gui.visualize.PrintablePanel;

import javax.swing.JComboBox;
import javax.swing.JFrame;
import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.FlowLayout;
import java.awt.Font;
import java.awt.FontMetrics;
import java.awt.Graphics;
import java.awt.event.ComponentAdapter;
import java.awt.event.ComponentEvent;
import java.awt.event.ItemEvent;
import java.awt.event.ItemListener;
import java.awt.event.MouseEvent;
import java.io.FileReader;
import java.util.ArrayList;

/**
 * Creates a panel that shows a visualization of an attribute in a dataset. For
 * nominal attribute it shows a bar plot, with each bar corresponding to each
 * nominal value of the attribute with its height equal to the frequecy that
 * value appears in the dataset. For numeric attributes, it displays a
 * histogram. The width of an interval in the histogram is calculated using
 * Scott's(1979) method: <br>
 * intervalWidth = Max(1, 3.49*Std.Dev*numInstances^(1/3)) Then the number of
 * intervals is calculated by: <br>
 * intervals = max(1, Math.round(Range/intervalWidth);
 * 
 * @author Ashraf M. Kibriya (amk14@cs.waikato.ac.nz)
 * @version $Revision$
 */
public class AttributeVisualizationPanel extends PrintablePanel {

    /** for serialization */
    private static final long serialVersionUID = -8650490488825371193L;

    /** This holds the current set of instances */
    protected Instances m_data;

    /**
     * This holds the attribute stats of the current attribute on display. It is
     * calculated in setAttribute(int idx) when it is called to set a new
     * attribute index.
     */
    protected AttributeStats m_as;

    /** Cache of attribute stats info for the current data set */
    protected AttributeStats[] m_asCache;

    /**
     * This holds the index of the current attribute on display and should be set
     * through setAttribute(int idx).
     */
    protected int m_attribIndex;

    /**
     * This holds the max value of the current attribute. In case of nominal
     * attribute it is the highest count that a nominal value has in the attribute
     * (given by m_as.nominalWeights[i]), otherwise in case of numeric attribute
     * it is simply the maximum value present in the attribute (given by
     * m_as.numericStats.max). It is used to calculate the ratio of the height of
     * the bars with respect to the height of the display area.
     */
    protected double m_maxValue;

    /**
     * This array holds the count (or height) for the each of the bars in a
     * barplot or a histogram. In case of barplots (and current attribute being
     * nominal) its length (and the number of bars) is equal to the number of
     * nominal values in the current attribute, with each field of the array being
     * equal to the count of each nominal that it represents (the count of ith
     * nominal value of an attribute is given by m_as.nominalWeights[i]). Whereas,
     * in case of histograms (and current attribute being numeric) the width of
     * its intervals is calculated by Scott's(1979) method: <br>
     * intervalWidth = Max(1, 3.49*Std.Dev*numInstances^(1/3)) And the number of
     * intervals by: <br>
     * intervals = max(1, Math.round(Range/intervalWidth); Then each field of this
     * array contains the number of values of the current attribute that fall in
     * the histogram interval that it represents. <br>
     * NOTE: The values of this array are only calculated if the class attribute
     * is not set or if it is numeric.
     */
    protected double[] m_histBarCounts;

    /**
     * This array holds the per class count (or per class height) of the each of
     * the bars in a barplot or a histogram. For nominal attributes the format is: <br>
     * m_histBarClassCounts[nominalValue][classValue+1]. For numeric attributes
     * the format is: <br>
     * m_histBarClassCounts[interval][classValues+1], <br>
     * where the number of intervals is calculated by the Scott's method as
     * mentioned above. The array is initialized to have 1+numClasses to
     * accomodate for instances with missing class value. The ones with missing
     * class value are displayed as a black sub par in a histogram or a barplot.
     * 
     * NOTE: The values of this array are only calculated if the class attribute
     * is set and it is nominal.
     */
    SparseInstance m_histBarClassCounts[];

    /**
     * Contains the range of each bar in a histogram. It is used to work out the
     * range of bar the mouse pointer is on in getToolTipText().
     */
    protected double m_barRange;

    /** Contains the current class index. */
    protected int m_classIndex;

    /**
     * This stores the BarCalc or HistCalc thread while a new barplot or histogram
     * is being calculated.
     */
    private Thread m_hc;

    /** True if the thread m_hc above is running. */
    private boolean m_threadRun = false;

    private boolean m_doneCurrentAttribute = false;
    private boolean m_displayCurrentAttribute = false;

    /**
     * This stores and lets the user select a class attribute. It also has an
     * entry "No Class" if the user does not want to set a class attribute for
     * colouring.
     */
    protected JComboBox m_colorAttrib;

    /**
     * Fontmetrics used to get the font size which is required for calculating
     * displayable area size, bar height ratio and width of strings that are
     * displayed on top of bars indicating their count.
     */
    private final FontMetrics m_fm;

    /**
     * Lock variable to synchronize the different threads running currently in
     * this class. There are two to three threads in this class, AWT paint thread
     * which is handled differently in paintComponent() which checks on
     * m_threadRun to determine if it can perform full paint or not, the second
     * thread is the main execution thread and the third is the one represented by
     * m_hc which we start when we want to calculate the internal fields for a bar
     * plot or a histogram.
     */
    private final Integer m_locker = new Integer(1);

    // Image img;

    /**
     * Contains discrete colours for colouring of subbars of histograms and bar
     * plots when the class attribute is set and is nominal
     */
    private final ArrayList<Color> m_colorList = new ArrayList<Color>();

    /** default colour list */
    private static final Color[] m_defaultColors = { Color.blue, Color.red, Color.cyan, new Color(75, 123, 130),
            Color.pink, Color.green, Color.orange, new Color(255, 0, 255), new Color(255, 0, 0),
            new Color(0, 255, 0), };

    /**
     * Constructor - If used then the class will not show the class selection
     * combo box.
     */
    public AttributeVisualizationPanel() {
        this(false);
    }

    /**
     * Constructor.
     * 
     * @param showColouringOption - should be true if the class selection combo
     *          box is to be displayed with the histogram/barplot, or false
     *          otherwise. P.S: the combo box is always created it just won't be
     *          shown if showColouringOption is false.
     */
    public AttributeVisualizationPanel(boolean showColouringOption) {
        this.setFont(new Font("Default", Font.PLAIN, 9));
        m_fm = this.getFontMetrics(this.getFont());
        this.setToolTipText("");
        FlowLayout fl = new FlowLayout(FlowLayout.LEFT);
        this.setLayout(fl);
        this.addComponentListener(new ComponentAdapter() {
            @Override
            public void componentResized(ComponentEvent ce) {
                if (m_data != null) {
                    // calcGraph();
                }
            }
        });

        m_colorAttrib = new JComboBox();
        m_colorAttrib.addItemListener(new ItemListener() {
            @Override
            public void itemStateChanged(ItemEvent ie) {
                if (ie.getStateChange() == ItemEvent.SELECTED) {
                    m_classIndex = m_colorAttrib.getSelectedIndex() - 1;
                    if (m_as != null) {
                        setAttribute(m_attribIndex);
                    }
                }
            }
        });

        if (showColouringOption) {
            // m_colorAttrib.setVisible(false);
            this.add(m_colorAttrib);
            validate();
        }
    }

    /**
     * Sets the instances for use
     * 
     * @param newins a set of Instances
     */
    public void setInstances(Instances newins) {

        m_attribIndex = 0;
        m_as = null;
        m_data = new Instances(newins);
        if (m_colorAttrib != null) {
            m_colorAttrib.removeAllItems();
            m_colorAttrib.addItem("No class");
            for (int i = 0; i < m_data.numAttributes(); i++) {
                String type = "(" + Attribute.typeToStringShort(m_data.attribute(i)) + ")";
                m_colorAttrib.addItem(new String("Class: " + m_data.attribute(i).name() + " " + type));
            }
            if (m_data.classIndex() >= 0) {
                m_colorAttrib.setSelectedIndex(m_data.classIndex() + 1);
            } else {
                m_colorAttrib.setSelectedIndex(m_data.numAttributes());
            }
            // if (m_data.classIndex() >= 0) {
            // m_colorAttrib.setSelectedIndex(m_data.classIndex());
            // }
        }
        if (m_data.classIndex() >= 0) {
            m_classIndex = m_data.classIndex();
        } else {
            m_classIndex = m_data.numAttributes() - 1;
        }

        m_asCache = new AttributeStats[m_data.numAttributes()];
    }

    /**
     * Returns the class selection combo box if the parent component wants to
     * place it in itself or in some component other than this component.
     */
    public JComboBox getColorBox() {
        return m_colorAttrib;
    }

    /**
     * Get the coloring (class) index for the plot
     * 
     * @return an <code>int</code> value
     */
    public int getColoringIndex() {
        return m_classIndex; // m_colorAttrib.getSelectedIndex();
    }

    /**
     * Set the coloring (class) index for the plot
     * 
     * @param ci an <code>int</code> value
     */
    public void setColoringIndex(int ci) {
        m_classIndex = ci;
        if (m_colorAttrib != null) {
            m_colorAttrib.setSelectedIndex(ci + 1);
        } else {
            setAttribute(m_attribIndex);
        }
    }

    /**
     * Tells the panel which attribute to visualize.
     * 
     * @param index The index of the attribute
     */
    public void setAttribute(int index) {

        synchronized (m_locker) {
            // m_threadRun = true;
            m_threadRun = false;
            m_doneCurrentAttribute = false;
            m_displayCurrentAttribute = true;
            // if(m_hc!=null && m_hc.isAlive()) m_hc.stop();
            m_attribIndex = index;
            if (m_asCache[index] != null) {
                m_as = m_asCache[index];
            } else {
                m_asCache[index] = m_data.attributeStats(index);
                m_as = m_asCache[index];
            }
            // m_as = m_data.attributeStats(m_attribIndex);
            // m_classIndex = m_colorAttrib.getSelectedIndex();
        }

        this.repaint();
        // calcGraph();
    }

    /**
     * Recalculates the barplot or histogram to display, required usually when the
     * attribute is changed or the component is resized.
     */
    public void calcGraph(int panelWidth, int panelHeight) {

        synchronized (m_locker) {
            m_threadRun = true;
            if (m_as.nominalWeights != null) {
                m_hc = new BarCalc(panelWidth, panelHeight);
                m_hc.setPriority(Thread.MIN_PRIORITY);
                m_hc.start();
            } else if (m_as.numericStats != null) {
                m_hc = new HistCalc();
                m_hc.setPriority(Thread.MIN_PRIORITY);
                m_hc.start();
            } else {
                m_histBarCounts = null;
                m_histBarClassCounts = null;
                m_doneCurrentAttribute = true;
                m_threadRun = false;
                this.repaint();
            }
        }
    }

    /**
     * Internal class that calculates the barplot to display, in a separate
     * thread. In particular it initializes some of the crucial internal fields
     * required by paintComponent() to display the histogram for the current
     * attribute. These include: m_histBarCounts or m_histBarClassCounts,
     * m_maxValue and m_colorList.
     */
    private class BarCalc extends Thread {
        private final int m_panelWidth;

        public BarCalc(int panelWidth, int panelHeight) {
            m_panelWidth = panelWidth;
        }

        @Override
        public void run() {
            synchronized (m_locker) {
                // there is no use doing/displaying anything if the resolution
                // of the panel is less than the number of values for this attribute
                if (m_data.attribute(m_attribIndex).numValues() > m_panelWidth) {
                    m_histBarClassCounts = null;
                    m_threadRun = false;
                    m_doneCurrentAttribute = true;
                    m_displayCurrentAttribute = false;
                    AttributeVisualizationPanel.this.repaint();
                    return;
                }

                if ((m_classIndex >= 0) && (m_data.attribute(m_classIndex).isNominal())) {
                    SparseInstance histClassCounts[];
                    histClassCounts = new SparseInstance[m_data.attribute(m_attribIndex).numValues()];
                    // [m_data.attribute(m_classIndex).numValues()+1];

                    if (m_as.nominalWeights.length > 0) {
                        m_maxValue = m_as.nominalWeights[0];
                        for (int i = 0; i < m_data.attribute(m_attribIndex).numValues(); i++) {
                            if (m_as.nominalWeights[i] > m_maxValue) {
                                m_maxValue = m_as.nominalWeights[i];
                            }
                        }
                    } else {
                        m_maxValue = 0;
                    }

                    if (m_colorList.size() == 0) {
                        m_colorList.add(Color.black);
                    }
                    for (int i = m_colorList.size(); i < m_data.attribute(m_classIndex).numValues() + 1; i++) {
                        Color pc = m_defaultColors[(i - 1) % 10];
                        int ija = (i - 1) / 10;
                        ija *= 2;

                        for (int j = 0; j < ija; j++) {
                            pc = pc.darker();
                        }

                        m_colorList.add(pc);
                    }

                    // first sort data on attribute values
                    m_data.sort(m_attribIndex);
                    double[] tempClassCounts = null;
                    int tempAttValueIndex = -1;

                    for (int k = 0; k < m_data.numInstances(); k++) {
                        // System.out.println("attrib: "+
                        // m_data.instance(k).value(m_attribIndex)+
                        // " class: "+
                        // m_data.instance(k).value(m_classIndex));
                        if (!m_data.instance(k).isMissing(m_attribIndex)) {
                            // check to see if we need to allocate some space here
                            if (m_data.instance(k).value(m_attribIndex) != tempAttValueIndex) {
                                if (tempClassCounts != null) {
                                    // set up the sparse instance for the previous bar (if any)
                                    int numNonZero = 0;
                                    for (double tempClassCount : tempClassCounts) {
                                        if (tempClassCount > 0) {
                                            numNonZero++;
                                        }
                                    }
                                    double[] nonZeroVals = new double[numNonZero];
                                    int[] nonZeroIndices = new int[numNonZero];
                                    int count = 0;
                                    for (int z = 0; z < tempClassCounts.length; z++) {
                                        if (tempClassCounts[z] > 0) {
                                            nonZeroVals[count] = tempClassCounts[z];
                                            nonZeroIndices[count++] = z;
                                        }
                                    }
                                    SparseInstance tempS = new SparseInstance(1.0, nonZeroVals, nonZeroIndices,
                                            tempClassCounts.length);
                                    histClassCounts[tempAttValueIndex] = tempS;
                                }

                                tempClassCounts = new double[m_data.attribute(m_classIndex).numValues() + 1];
                                tempAttValueIndex = (int) m_data.instance(k).value(m_attribIndex);

                                /*
                                 * histClassCounts[(int)m_data.instance(k).value(m_attribIndex)]
                                 * = new double[m_data.attribute(m_classIndex).numValues()+1];
                                 */
                            }
                            if (m_data.instance(k).isMissing(m_classIndex)) {
                                /*
                                 * histClassCounts[(int)m_data.instance(k).value(m_attribIndex)]
                                 * [0] += m_data.instance(k).weight();
                                 */
                                tempClassCounts[0] += m_data.instance(k).weight();
                            } else {
                                tempClassCounts[(int) m_data.instance(k).value(m_classIndex) + 1] += m_data
                                        .instance(k).weight();

                                /*
                                 * histClassCounts[(int)m_data.instance(k).value(m_attribIndex)]
                                 * [(int)m_data.instance(k).value(m_classIndex)+1] +=
                                 * m_data.instance(k).weight();
                                 */
                            }
                        }
                    }

                    // set up sparse instance for last bar?
                    if (tempClassCounts != null) {
                        // set up the sparse instance for the previous bar (if any)
                        int numNonZero = 0;
                        for (double tempClassCount : tempClassCounts) {
                            if (tempClassCount > 0) {
                                numNonZero++;
                            }
                        }
                        double[] nonZeroVals = new double[numNonZero];
                        int[] nonZeroIndices = new int[numNonZero];
                        int count = 0;
                        for (int z = 0; z < tempClassCounts.length; z++) {
                            if (tempClassCounts[z] > 0) {
                                nonZeroVals[count] = tempClassCounts[z];
                                nonZeroIndices[count++] = z;
                            }
                        }
                        SparseInstance tempS = new SparseInstance(1.0, nonZeroVals, nonZeroIndices,
                                tempClassCounts.length);
                        histClassCounts[tempAttValueIndex] = tempS;
                    }

                    // for(int i=0; i<histClassCounts.length; i++) {
                    // int sum=0;
                    // for(int j=0; j<histClassCounts[i].length; j++) {
                    // sum = sum+histClassCounts[i][j];
                    // }
                    // System.out.println("histCount: "+sum+" Actual: "+
                    // m_as.nominalWeights[i]);
                    // }

                    m_threadRun = false;
                    m_doneCurrentAttribute = true;
                    m_displayCurrentAttribute = true;
                    m_histBarClassCounts = histClassCounts;
                    // Image tmpImg = new BufferedImage(getWidth(), getHeight(),
                    // BufferedImage.TYPE_INT_RGB);
                    // drawGraph( tmpImg.getGraphics() );
                    // img = tmpImg;
                    AttributeVisualizationPanel.this.repaint();
                } else {
                    double histCounts[];
                    histCounts = new double[m_data.attribute(m_attribIndex).numValues()];

                    if (m_as.nominalWeights.length > 0) {
                        m_maxValue = m_as.nominalWeights[0];
                        for (int i = 0; i < m_data.attribute(m_attribIndex).numValues(); i++) {
                            if (m_as.nominalWeights[i] > m_maxValue) {
                                m_maxValue = m_as.nominalWeights[i];
                            }
                        }
                    } else {
                        m_maxValue = 0;
                    }

                    for (int k = 0; k < m_data.numInstances(); k++) {
                        if (!m_data.instance(k).isMissing(m_attribIndex)) {
                            histCounts[(int) m_data.instance(k).value(m_attribIndex)] += m_data.instance(k)
                                    .weight();
                        }
                    }
                    m_threadRun = false;
                    m_displayCurrentAttribute = true;
                    m_doneCurrentAttribute = true;
                    m_histBarCounts = histCounts;
                    // Image tmpImg = new BufferedImage(getWidth(), getHeight(),
                    // BufferedImage.TYPE_INT_RGB);
                    // drawGraph( tmpImg.getGraphics() );
                    // img = tmpImg;
                    AttributeVisualizationPanel.this.repaint();
                }
            } // end synchronized
        } // end run()
    }

    /**
     * Internal class that calculates the histogram to display, in a separate
     * thread. In particular it initializes some of the crucial internal fields
     * required by paintComponent() to display the histogram for the current
     * attribute. These include: m_histBarCounts or m_histBarClassCounts,
     * m_maxValue and m_colorList.
     */
    private class HistCalc extends Thread {
        @Override
        public void run() {
            synchronized (m_locker) {
                if ((m_classIndex >= 0) && (m_data.attribute(m_classIndex).isNominal())) {

                    int intervals;
                    double intervalWidth = 0.0;

                    // This uses the M.P.Wand's method to calculate the histogram's
                    // interval width. See "Data-Based Choice of Histogram Bin Width", in
                    // The American Statistician, Vol. 51, No. 1, Feb., 1997, pp. 59-64.
                    // intervalWidth = Math.pow(6D/( -psi(2,
                    // g21())*m_data.numInstances()),
                    // 1/3D );

                    // This uses the Scott's method to calculate the histogram's interval
                    // width. See "On optimal and data-based histograms".
                    // See Biometrika, 66, 605-610 OR see the same paper mentioned above.
                    intervalWidth = 3.49 * m_as.numericStats.stdDev * Math.pow(m_data.numInstances(), -1 / 3D);
                    // The Math.max is introduced to remove the possibility of
                    // intervals=0 and =NAN that can happen if respectively all the
                    // numeric
                    // values are the same or the interval width is evaluated to zero.
                    intervals = Math.max(1,
                            (int) Math.round((m_as.numericStats.max - m_as.numericStats.min) / intervalWidth));

                    // System.out.println("Max: "+m_as.numericStats.max+
                    // " Min: "+m_as.numericStats.min+
                    // " stdDev: "+m_as.numericStats.stdDev+
                    // "intervalWidth: "+intervalWidth);

                    // The number 4 below actually represents a padding of 3 pixels on
                    // each side of the histogram, and is also reflected in other parts of
                    // the code in the shape of numerical constants like "6" here.
                    if (intervals > AttributeVisualizationPanel.this.getWidth()) {
                        intervals = AttributeVisualizationPanel.this.getWidth() - 6;
                        if (intervals < 1) {
                            intervals = 1;
                        }
                    }
                    double histClassCounts[][] = new double[intervals][m_data.attribute(m_classIndex).numValues()
                            + 1];

                    double barRange = (m_as.numericStats.max - m_as.numericStats.min) / histClassCounts.length;

                    m_maxValue = 0;

                    if (m_colorList.size() == 0) {
                        m_colorList.add(Color.black);
                    }
                    for (int i = m_colorList.size(); i < m_data.attribute(m_classIndex).numValues() + 1; i++) {
                        Color pc = m_defaultColors[(i - 1) % 10];
                        int ija = (i - 1) / 10;
                        ija *= 2;
                        for (int j = 0; j < ija; j++) {
                            pc = pc.darker();
                        }
                        m_colorList.add(pc);
                    }

                    for (int k = 0; k < m_data.numInstances(); k++) {
                        int t = 0; // This holds the interval that the attibute value of the
                                   // new instance belongs to.
                        try {
                            if (!m_data.instance(k).isMissing(m_attribIndex)) {
                                // 1. see footnote at the end of this file
                                t = (int) Math.ceil(
                                        (float) ((m_data.instance(k).value(m_attribIndex) - m_as.numericStats.min)
                                                / barRange));
                                if (t == 0) {
                                    if (m_data.instance(k).isMissing(m_classIndex)) {
                                        histClassCounts[t][0] += m_data.instance(k).weight();
                                    } else {
                                        histClassCounts[t][(int) m_data.instance(k).value(m_classIndex)
                                                + 1] += m_data.instance(k).weight();
                                        // if(histCounts[t]>m_maxValue)
                                        // m_maxValue = histCounts[t];
                                    }
                                } else {
                                    if (m_data.instance(k).isMissing(m_classIndex)) {
                                        histClassCounts[t - 1][0] += m_data.instance(k).weight();
                                    } else {
                                        histClassCounts[t - 1][(int) m_data.instance(k).value(m_classIndex)
                                                + 1] += m_data.instance(k).weight();
                                        // if(histCounts[t-1]>m_maxValue)
                                        // m_maxValue = histCounts[t-1];
                                    }
                                }
                            }
                        } catch (ArrayIndexOutOfBoundsException ae) {
                            System.out.println("t:" + (t) + " barRange:" + barRange + " histLength:"
                                    + histClassCounts.length + " value:" + m_data.instance(k).value(m_attribIndex)
                                    + " min:" + m_as.numericStats.min + " sumResult:"
                                    + (m_data.instance(k).value(m_attribIndex) - m_as.numericStats.min)
                                    + " divideResult:"
                                    + (float) ((m_data.instance(k).value(m_attribIndex) - m_as.numericStats.min)
                                            / barRange)
                                    + " finalResult:" + Math.ceil((float) ((m_data.instance(k).value(m_attribIndex)
                                            - m_as.numericStats.min) / barRange)));
                        }
                    }
                    for (double[] histClassCount : histClassCounts) {
                        double sum = 0;
                        for (double element : histClassCount) {
                            sum = sum + element;
                        }
                        if (m_maxValue < sum) {
                            m_maxValue = sum;
                        }
                    }

                    // convert to sparse instances
                    SparseInstance[] histClassCountsSparse = new SparseInstance[histClassCounts.length];

                    for (int i = 0; i < histClassCounts.length; i++) {
                        int numSparseValues = 0;
                        for (int j = 0; j < histClassCounts[i].length; j++) {
                            if (histClassCounts[i][j] > 0) {
                                numSparseValues++;
                            }
                        }
                        double[] sparseValues = new double[numSparseValues];
                        int[] sparseIndices = new int[numSparseValues];
                        int count = 0;
                        for (int j = 0; j < histClassCounts[i].length; j++) {
                            if (histClassCounts[i][j] > 0) {
                                sparseValues[count] = histClassCounts[i][j];
                                sparseIndices[count++] = j;
                            }
                        }

                        SparseInstance tempS = new SparseInstance(1.0, sparseValues, sparseIndices,
                                histClassCounts[i].length);
                        histClassCountsSparse[i] = tempS;

                    }

                    m_histBarClassCounts = histClassCountsSparse;
                    m_barRange = barRange;

                } else { // else if the class attribute is numeric or the class is not
                         // set

                    int intervals;
                    double intervalWidth;
                    // At the time of this coding the
                    // possibility of datasets with zero instances
                    // was being dealt with in the
                    // PreProcessPanel of weka Explorer.

                    // old method of calculating number of intervals
                    // intervals = m_as.totalCount>10 ?
                    // (int)(m_as.totalCount*0.1):(int)m_as.totalCount;

                    // This uses the M.P.Wand's method to calculate the histogram's
                    // interval width. See "Data-Based Choice of Histogram Bin Width", in
                    // The American Statistician, Vol. 51, No. 1, Feb., 1997, pp. 59-64.
                    // intervalWidth = Math.pow(6D/(-psi(2, g21())*m_data.numInstances()
                    // ),
                    // 1/3D );

                    // This uses the Scott's method to calculate the histogram's interval
                    // width. See "On optimal and data-based histograms".
                    // See Biometrika, 66, 605-610 OR see the same paper mentioned above.
                    intervalWidth = 3.49 * m_as.numericStats.stdDev * Math.pow(m_data.numInstances(), -1 / 3D);
                    // The Math.max is introduced to remove the possibility of
                    // intervals=0 and =NAN that can happen if respectively all the
                    // numeric
                    // values are the same or the interval width is evaluated to zero.
                    intervals = Math.max(1,
                            (int) Math.round((m_as.numericStats.max - m_as.numericStats.min) / intervalWidth));

                    // The number 4 below actually represents a padding of 3 pixels on
                    // each side of the histogram, and is also reflected in other parts of
                    // the code in the shape of numerical constants like "6" here.
                    if (intervals > AttributeVisualizationPanel.this.getWidth()) {
                        intervals = AttributeVisualizationPanel.this.getWidth() - 6;
                        if (intervals < 1) {
                            intervals = 1;
                        }
                    }

                    double[] histCounts = new double[intervals];
                    double barRange = (m_as.numericStats.max - m_as.numericStats.min) / histCounts.length;

                    m_maxValue = 0;

                    for (int k = 0; k < m_data.numInstances(); k++) {
                        int t = 0; // This holds the interval to which the current
                                   // attribute's
                                   // value of this particular instance k belongs to.

                        if (m_data.instance(k).isMissing(m_attribIndex)) {
                            continue; // ignore missing values
                        }

                        try {
                            // 1. see footnote at the end of this file
                            t = (int) Math
                                    .ceil((float) ((m_data.instance(k).value(m_attribIndex) - m_as.numericStats.min)
                                            / barRange));
                            if (t == 0) {
                                histCounts[t] += m_data.instance(k).weight();
                                if (histCounts[t] > m_maxValue) {
                                    m_maxValue = histCounts[t];
                                }
                            } else {
                                histCounts[t - 1] += m_data.instance(k).weight();
                                if (histCounts[t - 1] > m_maxValue) {
                                    m_maxValue = histCounts[t - 1];
                                }
                            }
                        } catch (ArrayIndexOutOfBoundsException ae) {
                            ae.printStackTrace();
                            System.out.println("t:" + (t) + " barRange:" + barRange + " histLength:"
                                    + histCounts.length + " value:" + m_data.instance(k).value(m_attribIndex)
                                    + " min:" + m_as.numericStats.min + " sumResult:"
                                    + (m_data.instance(k).value(m_attribIndex) - m_as.numericStats.min)
                                    + " divideResult:"
                                    + (float) ((m_data.instance(k).value(m_attribIndex) - m_as.numericStats.min)
                                            / barRange)
                                    + " finalResult:" + Math.ceil((float) ((m_data.instance(k).value(m_attribIndex)
                                            - m_as.numericStats.min) / barRange)));
                        }
                    }
                    m_histBarCounts = histCounts;
                    m_barRange = barRange;
                }

                m_threadRun = false;
                m_displayCurrentAttribute = true;
                m_doneCurrentAttribute = true;
                // Image tmpImg = new BufferedImage(getWidth(), getHeight(),
                // BufferedImage.TYPE_INT_RGB);
                // drawGraph( tmpImg.getGraphics() );
                // img = tmpImg;
                AttributeVisualizationPanel.this.repaint();
            }
        }

        /****
         * Code for M.P.Wand's method of histogram bin width selection. There is
         * some problem with it. It always comes up -ve value which is raised to the
         * power 1/3 and gives an NAN. private static final int M=400; private
         * double psi(int r, double g) { double val;
         * 
         * double sum=0.0; for(int i=0; i<M; i++) { double valCjKj=0.0; for(int j=0;
         * j<M; j++) { valCjKj += c(j) * k(r, j-i, g); } sum += valCjKj*c(i); }
         * 
         * val = Math.pow(m_data.numInstances(), -2) * sum;
         * //System.out.println("psi returns: "+val); return val; } private double
         * g21() { double val;
         * 
         * val = Math.pow(2 / ( Math.sqrt(2D*Math.PI)*psi(4, g22()) *
         * m_data.numInstances() ), 1/5D) * Math.sqrt(2) * m_as.numericStats.stdDev;
         * //System.out.println("g21 returns: "+val); return val; } private double
         * g22() { double val;
         * 
         * val = Math.pow( 2D/(5*m_data.numInstances()), 1/7D) * Math.sqrt(2) *
         * m_as.numericStats.stdDev; //System.out.println("g22 returns: "+val);
         * return val; } private double c(int j) { double val=0.0; double sigma =
         * (m_as.numericStats.max - m_as.numericStats.min)/(M-1);
         * 
         * //System.out.println("In c before doing the sum we have");
         * //System.out.println("max: " +m_as.numericStats.max+" min: "+ //
         * m_as.numericStats.min+" sigma: "+sigma);
         * 
         * for(int i=0; i<m_data.numInstances(); i++) {
         * if(!m_data.instance(i).isMissing(m_attribIndex)) val += Math.max( 0, ( 1
         * - Math.abs( Math.pow(sigma, -1)*(m_data.instance(i).value(m_attribIndex)
         * - j) ) ) ); } //System.out.println("c returns: "+val); return val; }
         * private double k(int r, int j, double g) { double val; double sigma =
         * (m_as.numericStats.max - m_as.numericStats.min)/(M-1);
         * //System.out.println("Before calling L we have");
         * //System.out.println("Max: "
         * +m_as.numericStats.max+" Min: "+m_as.numericStats.min+"\n"+ //
         * "r: "+r+" j: "+j+" g: "+g); val = Math.pow( g, -r-1) * L(sigma*j/g);
         * //System.out.println("k returns: "+val); return val; } private double
         * L(double x) { double val;
         * 
         * val = Math.pow( 2*Math.PI, -1/2D ) * Math.exp( -(x*x)/2D );
         * //System.out.println("L returns: "+val); return val; } End of Wand's
         * method
         */
    }

    /**
     * Returns "&lt;nominal value&gt; [&lt;nominal value count&gt;]" if displaying
     * a bar plot and mouse is on some bar. If displaying histogram then it <li>
     * returns "count &lt;br&gt; [&lt;bars Range&gt;]" if mouse is on the first
     * bar.</li> <li>returns "count &lt;br&gt; (&lt;bar's Range&gt;]" if mouse is
     * on some bar other than the first one.</li> Otherwise it returns ""
     * 
     * @param ev The mouse event
     */
    @Override
    public String getToolTipText(MouseEvent ev) {

        if (m_as != null && m_as.nominalWeights != null) { // if current attrib is
                                                           // nominal

            float intervalWidth = this.getWidth() / (float) m_as.nominalWeights.length;
            double heightRatio;
            int barWidth, x = 0;

            // if intervalWidth is at least six then bar width is 80% of intervalwidth
            if (intervalWidth > 5) {
                barWidth = (int) Math.floor(intervalWidth * 0.8F);
            } else {
                barWidth = 1; // Otherwise barwidth is 1 & padding would be at least 1.
            }

            // initializing x to maximum of 1 or 10% of interval width (i.e. half of
            // the padding which is 20% of interval width, as there is 10% on each
            // side of the bar) so that it points to the start of the 1st bar
            x = x + (int) ((Math.floor(intervalWidth * 0.1F)) < 1 ? 1 : (Math.floor(intervalWidth * 0.1F)));

            // Adding to x the appropriate value so that it points to the 1st bar of
            // our "centered" barplot. If subtracting barplots width from panel width
            // gives <=2 then the barplot is not centered.
            if (this.getWidth() - (m_as.nominalWeights.length * barWidth
                    + (int) ((Math.floor(intervalWidth * 0.2F)) < 1 ? 1 : (Math.floor(intervalWidth * 0.2F)))
                            * m_as.nominalWeights.length) > 2) {

                // The following amounts to adding to x the half of the area left after
                // subtracting from the components width the width of the whole barplot
                // (i.e. width of all the bars plus the width of all the bar paddings,
                // thereby equaling to the whole barplot), since our barplot is
                // centered.
                x += (this.getWidth() - (m_as.nominalWeights.length * barWidth
                        + (int) ((Math.floor(intervalWidth * 0.2F)) < 1 ? 1 : (Math.floor(intervalWidth * 0.2F)))
                                * m_as.nominalWeights.length))
                        / 2;
            }

            for (int i = 0; i < m_as.nominalWeights.length; i++) {
                heightRatio = (this.getHeight() - (double) m_fm.getHeight()) / m_maxValue;

                // if our mouse is on a bar then return the count of this bar in our
                // barplot
                if (ev.getX() >= x && ev.getX() <= x + barWidth
                        && ev.getY() >= this.getHeight() - Math.round(m_as.nominalWeights[i] * heightRatio)) {
                    return (m_data.attribute(m_attribIndex).value(i) + " ["
                            + Utils.doubleToString(m_as.nominalWeights[i], 3) + "]");
                }
                // otherwise advance x to next bar and check that. Add barwidth to x
                // and padding which is max(1, 20% of interval width)
                x = x + barWidth
                        + (int) ((Math.floor(intervalWidth * 0.2F)) < 1 ? 1 : (Math.floor(intervalWidth * 0.2F)));
            }
        } else if (m_threadRun == false && // if attrib is numeric
                (m_histBarCounts != null || m_histBarClassCounts != null)) {

            int x = 0, barWidth;
            double bar = m_as.numericStats.min;

            // if the class attribute is set and it is nominal
            if ((m_classIndex >= 0) && (m_data.attribute(m_classIndex).isNominal())) {
                // there is 3 pixels of padding on each side of the histogram
                // the barwidth is 1 if after removing the padding its width is less
                // then the displayable width
                barWidth = ((this.getWidth() - 6) / m_histBarClassCounts.length) < 1 ? 1
                        : ((this.getWidth() - 6) / m_histBarClassCounts.length);

                // initializing x to 3 adding appropriate value to make it point to the
                // start of the 1st bar of our "centered" histogram.
                x = 3;
                if ((this.getWidth() - (x + m_histBarClassCounts.length * barWidth)) > 5) {
                    x += (this.getWidth() - (x + m_histBarClassCounts.length * barWidth)) / 2;
                }

                if (ev.getX() - x >= 0) {
                    // The temp holds the index of the current interval that we are
                    // looking
                    // at
                    int temp = (int) ((ev.getX() - x) / (barWidth + 0.0000000001));
                    if (temp == 0) { // handle the special case temp==0. see footnote 1
                        double sum = 0;
                        for (int k = 0; k < m_histBarClassCounts[0].numValues(); k++) {
                            sum += m_histBarClassCounts[0].valueSparse(k);
                        }
                        // return the count of the interval mouse is pointing to plus
                        // the range of values that fall into this interval
                        return ("<html><center><font face=Dialog size=-1>" + Utils.doubleToString(sum, 3) + "<br>"
                                + "[" + Utils.doubleToString(bar + m_barRange * temp, 3) + ", "
                                + Utils.doubleToString((bar + m_barRange * (temp + 1)), 3) + "]"
                                + "</font></center></html>");
                    } else if (temp < m_histBarClassCounts.length) { // handle case
                                                                     // temp!=0
                        double sum = 0;
                        for (int k = 0; k < m_histBarClassCounts[temp].numValues(); k++) {
                            sum += m_histBarClassCounts[temp].valueSparse(k);
                        }
                        // return the count of the interval mouse is pointing to plus
                        // the range of values that fall into this interval
                        return ("<html><center><font face=Dialog size=-1>" + Utils.doubleToString(sum, 3) + "<br>("
                                + Utils.doubleToString(bar + m_barRange * temp, 3) + ", "
                                + Utils.doubleToString((bar + m_barRange * (temp + 1)), 3)
                                + "]</font></center></html>");
                    }
                }
            } else { // else if the class attribute is not set or is numeric
                barWidth = ((this.getWidth() - 6) / m_histBarCounts.length) < 1 ? 1
                        : ((this.getWidth() - 6) / m_histBarCounts.length);

                // initializing x to 3 adding appropriate value to make it point to the
                // start of the 1st bar of our "centered" histogram.
                x = 3;
                if ((this.getWidth() - (x + m_histBarCounts.length * barWidth)) > 5) {
                    x += (this.getWidth() - (x + m_histBarCounts.length * barWidth)) / 2;
                }

                if (ev.getX() - x >= 0) {
                    // Temp holds the index of the current bar we are looking at.
                    int temp = (int) ((ev.getX() - x) / (barWidth + 0.0000000001));

                    // return interval count as well as its range
                    if (temp == 0) {
                        return ("<html><center><font face=Dialog size=-1>" + m_histBarCounts[0] + "<br>" + "["
                                + Utils.doubleToString(bar + m_barRange * temp, 3) + ", "
                                + Utils.doubleToString((bar + m_barRange * (temp + 1)), 3) + "]"
                                + "</font></center></html>");
                    } else if (temp < m_histBarCounts.length) {
                        return ("<html><center><font face=Dialog size=-1>" + m_histBarCounts[temp] + "<br>" + "("
                                + Utils.doubleToString(bar + m_barRange * temp, 3) + ", "
                                + Utils.doubleToString((bar + m_barRange * (temp + 1)), 3) + "]"
                                + "</font></center></html>");
                    }
                }
            }
        }
        return PrintableComponent.getToolTipText(m_Printer);
    }

    /**
     * Paints this component
     * 
     * @param g The graphics object for this component
     */
    @Override
    public void paintComponent(Graphics g) {
        g.setColor(Color.WHITE);
        g.fillRect(0, 0, this.getWidth(), this.getHeight());
        g.setColor(Color.BLACK);

        if (m_as != null) { // If calculations have been done and histogram/barplot
            if (!m_doneCurrentAttribute && !m_threadRun) {
                calcGraph(this.getWidth(), this.getHeight());
            }
            if (m_threadRun == false && m_displayCurrentAttribute) { // calculation
                                                                     // thread is not
                                                                     // running
                int buttonHeight = 0;

                if (m_colorAttrib != null) {
                    buttonHeight = m_colorAttrib.getHeight() + m_colorAttrib.getLocation().y;
                }

                // if current attribute is nominal then draw barplot.
                if (m_as.nominalWeights != null && (m_histBarClassCounts != null || m_histBarCounts != null)) {
                    double heightRatio, intervalWidth;
                    int x = 0, y = 0, barWidth;

                    // if the class attribute is set and is nominal then draw coloured
                    // subbars for each bar
                    if ((m_classIndex >= 0) && (m_data.attribute(m_classIndex).isNominal())) {

                        intervalWidth = (this.getWidth() / (float) m_histBarClassCounts.length);

                        // Barwidth is 80% of interval width.The remaining 20% is padding,
                        // 10% on each side of the bar. If interval width is less then 5 the
                        // 20% of that value is less than 1, in that case we use bar width
                        // of
                        // 1 and padding of 1 pixel on each side of the bar.
                        if (intervalWidth > 5) {
                            barWidth = (int) Math.floor(intervalWidth * 0.8F);
                        } else {
                            barWidth = 1;
                        }

                        // initializing x to 10% of interval width or to 1 if 10% is <1.
                        // This
                        // is essentially the LHS padding of the 1st bar.
                        x = x + (int) ((Math.floor(intervalWidth * 0.1F)) < 1 ? 1
                                : (Math.floor(intervalWidth * 0.1F)));

                        // Add appropriate value to x so that it starts at the 1st bar of
                        // a "centered" barplot.
                        if (this.getWidth() - (m_histBarClassCounts.length * barWidth
                                + (int) ((Math.floor(intervalWidth * 0.2F)) < 1 ? 1
                                        : (Math.floor(intervalWidth * 0.2F))) * m_histBarClassCounts.length) > 2) {
                            // We take the width of all the bars and all the paddings (20%
                            // of interval width), and subtract it from the width of the panel
                            // to get the extra space that would be left after drawing. We
                            // divide that space by 2 to get its mid-point and add that to our
                            // x, thus making the whole bar plot drawn centered in our
                            // component.
                            x += (this.getWidth() - (m_histBarClassCounts.length * barWidth
                                    + (int) ((Math.floor(intervalWidth * 0.2F)) < 1 ? 1
                                            : (Math.floor(intervalWidth * 0.2F))) * m_histBarClassCounts.length))
                                    / 2;
                        }

                        // this holds the count of the bar and will be calculated by adding
                        // up the counts of individual subbars. It is displayed at the top
                        // of each bar.
                        double sum = 0;
                        for (SparseInstance m_histBarClassCount : m_histBarClassCounts) {

                            // calculating the proportion of the components height compared to
                            // the maxvalue in our attribute, also taking into account the
                            // height of font to display bars count and the height of the
                            // class
                            // ComboBox.
                            heightRatio = (this.getHeight() - (double) m_fm.getHeight() - buttonHeight)
                                    / m_maxValue;
                            y = this.getHeight();
                            if (m_histBarClassCount != null) {
                                for (int j = 0; j < m_histBarClassCount.numAttributes(); j++) {
                                    sum = sum + m_histBarClassCount.value(j);
                                    y = (int) (y - Math.round(m_histBarClassCount.value(j) * heightRatio));
                                    // selecting the colour corresponding to the current class.
                                    g.setColor(m_colorList.get(j));
                                    g.fillRect(x, y, barWidth,
                                            (int) Math.round(m_histBarClassCount.value(j) * heightRatio));
                                    g.setColor(Color.black);
                                }
                            }
                            // drawing the bar count at the top of the bar if it is less than
                            // interval width. draw it 1px up to avoid touching the bar.
                            if (m_fm.stringWidth(Utils.doubleToString(sum, 1)) < intervalWidth) {
                                g.drawString(Utils.doubleToString(sum, 1), x, y - 1);
                            }
                            // advancing x to the next bar by adding bar width and padding
                            // of both the bars (i.e. RHS padding of the bar just drawn and
                            // LHS
                            // padding of the new bar).
                            x = x + barWidth + (int) ((Math.floor(intervalWidth * 0.2F)) < 1 ? 1
                                    : (Math.floor(intervalWidth * 0.2F)));
                            // reseting sum for the next bar.
                            sum = 0;

                        }
                    }
                    // else if class attribute is numeric or not set then draw black bars.
                    else {
                        intervalWidth = (this.getWidth() / (float) m_histBarCounts.length);

                        // same as in the case of nominal class (see inside of if stmt
                        // corresponding to the current else above).
                        if (intervalWidth > 5) {
                            barWidth = (int) Math.floor(intervalWidth * 0.8F);
                        } else {
                            barWidth = 1;
                        }

                        // same as in the case of nominal class (see inside of if stmt
                        // corresponding to the current else above).
                        x = x + (int) ((Math.floor(intervalWidth * 0.1F)) < 1 ? 1
                                : (Math.floor(intervalWidth * 0.1F)));

                        // same as in the case of nominal class
                        if (this.getWidth() - (m_histBarCounts.length * barWidth
                                + (int) ((Math.floor(intervalWidth * 0.2F)) < 1 ? 1
                                        : (Math.floor(intervalWidth * 0.2F))) * m_histBarCounts.length) > 2) {
                            x += (this.getWidth() - (m_histBarCounts.length * barWidth
                                    + (int) ((Math.floor(intervalWidth * 0.2F)) < 1 ? 1
                                            : (Math.floor(intervalWidth * 0.2F))) * m_histBarCounts.length))
                                    / 2;
                        }

                        for (double m_histBarCount : m_histBarCounts) {
                            // calculating the proportion of the height of the component
                            // compared to the maxValue in our attribute.
                            heightRatio = (this.getHeight() - (float) m_fm.getHeight() - buttonHeight) / m_maxValue;
                            y = (int) (this.getHeight() - Math.round(m_histBarCount * heightRatio));
                            g.fillRect(x, y, barWidth, (int) Math.round(m_histBarCount * heightRatio));
                            // draw the bar count if it's width is smaller than intervalWidth.
                            // draw it 1px above to avoid touching the bar.
                            if (m_fm.stringWidth(Utils.doubleToString(m_histBarCount, 1)) < intervalWidth) {
                                g.drawString(Utils.doubleToString(m_histBarCount, 1), x, y - 1);
                            }
                            // Advance x to the next bar by adding bar-width and padding
                            // of the bars (RHS padding of current bar & LHS padding of next
                            // bar).
                            x = x + barWidth + (int) ((Math.floor(intervalWidth * 0.2F)) < 1 ? 1
                                    : (Math.floor(intervalWidth * 0.2F)));
                        }
                    }

                } // <--end if m_as.nominalCount!=null
                  // if the current attribute is numeric then draw a histogram.
                else if (m_as.numericStats != null && (m_histBarClassCounts != null || m_histBarCounts != null)) {

                    double heightRatio;
                    int x = 0, y = 0, barWidth;

                    // If the class attribute is set and is not numeric then draw coloured
                    // subbars for the histogram bars
                    if ((m_classIndex >= 0) && (m_data.attribute(m_classIndex).isNominal())) {

                        // There is a padding of 3px on each side of the histogram.
                        barWidth = ((this.getWidth() - 6) / m_histBarClassCounts.length) < 1 ? 1
                                : ((this.getWidth() - 6) / m_histBarClassCounts.length);

                        // initializing x to start at the start of the 1st bar after
                        // padding.
                        x = 3;
                        // Adding appropriate value to x to account for a "centered"
                        // histogram
                        if ((this.getWidth() - (x + m_histBarClassCounts.length * barWidth)) > 5) {
                            // We take the current value of x (histogram's RHS padding) and
                            // add
                            // the barWidths of all the bars to it to us the size of
                            // our histogram. We subtract that from the width of the panel
                            // giving us the extra space that would be left if the histogram
                            // is
                            // drawn and divide that by 2 to get the midpoint of that extra
                            // space. That space is then added to our x, hence making the
                            // histogram centered.
                            x += (this.getWidth() - (x + m_histBarClassCounts.length * barWidth)) / 2;
                        }

                        for (SparseInstance m_histBarClassCount : m_histBarClassCounts) {
                            if (m_histBarClassCount != null) {
                                // Calculating height ratio. Leave space of 19 for an axis line
                                // at
                                // the bottom
                                heightRatio = (this.getHeight() - (float) m_fm.getHeight() - buttonHeight - 19)
                                        / m_maxValue;
                                y = this.getHeight() - 19;
                                // This would hold the count of the bar (sum of sub-bars).
                                double sum = 0;
                                for (int j = 0; j < m_histBarClassCount.numValues(); j++) {
                                    y = (int) (y - Math.round(m_histBarClassCount.valueSparse(j) * heightRatio));
                                    // System.out.println("Filling x:"+x+" y:"+y+" width:"+barWidth+
                                    // " height:"+
                                    // (m_histBarClassCounts[i][j]*heightRatio));
                                    // selecting the color corresponding to our class
                                    g.setColor(m_colorList.get(m_histBarClassCount.index(j)));
                                    // drawing the bar if its width is greater than 1
                                    if (barWidth > 1) {
                                        g.fillRect(x, y, barWidth,
                                                (int) Math.round(m_histBarClassCount.valueSparse(j) * heightRatio));
                                    } else if ((m_histBarClassCount.valueSparse(j) * heightRatio) > 0) {
                                        g.drawLine(x, y, x, (int) (y
                                                + Math.round(m_histBarClassCount.valueSparse(j) * heightRatio)));
                                    }
                                    g.setColor(Color.black);
                                    sum = sum + m_histBarClassCount.valueSparse(j);
                                }
                                // Drawing bar count on the top of the bar if it is < barWidth
                                if (m_fm.stringWidth(" " + Utils.doubleToString(sum, 1)) < barWidth) {
                                    g.drawString(" " + Utils.doubleToString(sum, 1), x, y - 1);
                                }
                                // Moving x to the next bar
                                x = x + barWidth;
                            }
                        }

                        // Now drawing the axis line at the bottom of the histogram
                        // initializing x again to the start of the plot
                        x = 3;
                        if ((this.getWidth() - (x + m_histBarClassCounts.length * barWidth)) > 5) {
                            x += (this.getWidth() - (x + m_histBarClassCounts.length * barWidth)) / 2;
                        }

                        g.drawLine(x, this.getHeight() - 17, (barWidth == 1)
                                ? x + barWidth * m_histBarClassCounts.length - 1
                                : x + barWidth * m_histBarClassCounts.length, this.getHeight() - 17); // axis
                                                                                                                                                                                                            // line --
                                                                                                                                                                                                            // see
                                                                                                                                                                                                            // footnote
                                                                                                                                                                                                            // 2.
                        g.drawLine(x, this.getHeight() - 16, x, this.getHeight() - 12); // minimum
                                                                                        // line
                        g.drawString(Utils.doubleToString(m_as.numericStats.min, 2), x,
                                this.getHeight() - 12 + m_fm.getHeight()); // minimum value
                        g.drawLine(x + (barWidth * m_histBarClassCounts.length) / 2, this.getHeight() - 16,
                                x + (barWidth * m_histBarClassCounts.length) / 2, this.getHeight() - 12); // median line
                        // Drawing median value. X position for drawing the value is: from
                        // start of the plot take the mid point and subtract from it half
                        // of the width of the value to draw.
                        g.drawString(Utils.doubleToString(m_as.numericStats.max / 2 + m_as.numericStats.min / 2, 2),
                                x + (barWidth * m_histBarClassCounts.length) / 2
                                        - m_fm.stringWidth(Utils.doubleToString(
                                                m_as.numericStats.max / 2 + m_as.numericStats.min / 2, 2)) / 2,
                                this.getHeight() - 12 + m_fm.getHeight()); // median value
                        g.drawLine(
                                (barWidth == 1) ? x + barWidth * m_histBarClassCounts.length - 1
                                        : x + barWidth * m_histBarClassCounts.length,
                                this.getHeight() - 16,
                                (barWidth == 1) ? x + barWidth * m_histBarClassCounts.length - 1
                                        : x + barWidth * m_histBarClassCounts.length,
                                this.getHeight() - 12); // maximum line
                        g.drawString(Utils.doubleToString(m_as.numericStats.max, 2),
                                (barWidth == 1) ? x + barWidth * m_histBarClassCounts.length
                                        - m_fm.stringWidth(Utils.doubleToString(m_as.numericStats.max, 2)) - 1
                                        : x + barWidth * m_histBarClassCounts.length
                                                - m_fm.stringWidth(Utils.doubleToString(m_as.numericStats.max, 2)),
                                this.getHeight() - 12 + m_fm.getHeight()); // maximum
                                                                                                                                                                                                                                                                                                                                                                                             // value --
                                                                                                                                                                                                                                                                                                                                                                                             // see 2.
                    } else { // if class attribute is numeric
                        // There is a padding of 3px on each side of the histogram.
                        barWidth = ((this.getWidth() - 6) / m_histBarCounts.length) < 1 ? 1
                                : ((this.getWidth() - 6) / m_histBarCounts.length);

                        // Same as above. Pls inside of the if stmt.
                        x = 3;
                        if ((this.getWidth() - (x + m_histBarCounts.length * barWidth)) > 5) {
                            x += (this.getWidth() - (x + m_histBarCounts.length * barWidth)) / 2;
                        }

                        // Same as above
                        for (double m_histBarCount : m_histBarCounts) {
                            // calculating the ration of the component's height compared to
                            // the maxValue in our current attribute. Leaving 19 pixels to
                            // draw the axis at the bottom of the histogram.
                            heightRatio = (this.getHeight() - (float) m_fm.getHeight() - buttonHeight - 19)
                                    / m_maxValue;
                            y = (int) (this.getHeight() - Math.round(m_histBarCount * heightRatio) - 19);
                            // System.out.println("Filling x:"+x+" y:"+y+" width:"+barWidth+
                            // " height:"+(m_histBarCounts[i]*heightRatio));
                            // same as in the if stmt above
                            if (barWidth > 1) {
                                g.drawRect(x, y, barWidth, (int) Math.round(m_histBarCount * heightRatio));
                            } else if ((m_histBarCount * heightRatio) > 0) {
                                g.drawLine(x, y, x, (int) (y + Math.round(m_histBarCount * heightRatio)));
                            }
                            if (m_fm.stringWidth(" " + Utils.doubleToString(m_histBarCount, 1)) < barWidth) {
                                g.drawString(" " + Utils.doubleToString(m_histBarCount, 1), x, y - 1);
                            }

                            x = x + barWidth;
                        }

                        // Now drawing the axis at the bottom of the histogram
                        x = 3;
                        if ((this.getWidth() - (x + m_histBarCounts.length * barWidth)) > 5) {
                            x += (this.getWidth() - (x + m_histBarCounts.length * barWidth)) / 2;
                        }

                        // This is exact the same as in the if stmt above. See the inside of
                        // the stmt for details
                        g.drawLine(x, this.getHeight() - 17,
                                (barWidth == 1) ? x + barWidth * m_histBarCounts.length - 1
                                        : x + barWidth * m_histBarCounts.length,
                                this.getHeight() - 17); // axis line
                        g.drawLine(x, this.getHeight() - 16, x, this.getHeight() - 12); // minimum
                                                                                        // line
                        g.drawString(Utils.doubleToString(m_as.numericStats.min, 2), x,
                                this.getHeight() - 12 + m_fm.getHeight()); // minimum value
                        g.drawLine(x + (barWidth * m_histBarCounts.length) / 2, this.getHeight() - 16,
                                x + (barWidth * m_histBarCounts.length) / 2, this.getHeight() - 12); // median line
                        g.drawString(Utils.doubleToString(m_as.numericStats.max / 2 + m_as.numericStats.min / 2, 2),
                                x + (barWidth * m_histBarCounts.length) / 2
                                        - m_fm.stringWidth(Utils.doubleToString(
                                                m_as.numericStats.max / 2 + m_as.numericStats.min / 2, 2)) / 2,
                                this.getHeight() - 12 + m_fm.getHeight()); // median value
                        g.drawLine((barWidth == 1) ? x
                                + barWidth * m_histBarCounts.length - 1 : x + barWidth * m_histBarCounts.length,
                                this.getHeight() - 16,
                                (barWidth == 1) ? x + barWidth
                                        * m_histBarCounts.length - 1 : x + barWidth * m_histBarCounts.length,
                                this.getHeight() - 12); // maximum
                                                                                                                                                                                                                                                                                                    // line
                        g.drawString(Utils.doubleToString(m_as.numericStats.max, 2),
                                (barWidth == 1) ? x + barWidth * m_histBarCounts.length
                                        - m_fm.stringWidth(Utils.doubleToString(m_as.numericStats.max, 2)) - 1
                                        : x + barWidth * m_histBarCounts.length
                                                - m_fm.stringWidth(Utils.doubleToString(m_as.numericStats.max, 2)),
                                this.getHeight() - 12 + m_fm.getHeight()); // maximum
                                                                                                                                                                                                                                                                                                                                                                                   // value
                    }
                    // System.out.println("barWidth:"+barWidth+
                    // " histBarCount:"+m_histBarCounts.length);

                } else {
                    g.clearRect(0, 0, this.getWidth(), this.getHeight());
                    g.drawString("Attribute is neither numeric nor nominal.",
                            this.getWidth() / 2 - m_fm.stringWidth("Attribute is neither numeric nor nominal.") / 2,
                            this.getHeight() / 2 - m_fm.getHeight() / 2);
                }
            } // <--end if of calculation thread
            else if (m_displayCurrentAttribute) { // if still calculation thread is
                                                  // running plot
                g.clearRect(0, 0, this.getWidth(), this.getHeight());
                g.drawString("Calculating. Please Wait...",
                        this.getWidth() / 2 - m_fm.stringWidth("Calculating. Please Wait...") / 2,
                        this.getHeight() / 2 - m_fm.getHeight() / 2);
            } else if (!m_displayCurrentAttribute) {
                g.clearRect(0, 0, this.getWidth(), this.getHeight());
                g.drawString("Too many values to display.",
                        this.getWidth() / 2 - m_fm.stringWidth("Too many values to display.") / 2,
                        this.getHeight() / 2 - m_fm.getHeight() / 2);
            }
        } // <--end if(m_as==null) this means
    }

    /**
     * Main method to test this class from command line
     * 
     * @param args The arff file and the index of the attribute to use
     */
    public static void main(String[] args) {
        if (args.length != 3) {
            final JFrame jf = new JFrame("AttribVisualization");
            AttributeVisualizationPanel ap = new AttributeVisualizationPanel();
            try {
                Instances ins = new Instances(new FileReader(args[0]));
                ap.setInstances(ins);
                System.out.println("Loaded: " + args[0] + "\nRelation: " + ap.m_data.relationName()
                        + "\nAttributes: " + ap.m_data.numAttributes());
                ap.setAttribute(Integer.parseInt(args[1]));
            } catch (Exception ex) {
                ex.printStackTrace();
                System.exit(-1);
            }
            System.out.println("The attributes are: ");
            for (int i = 0; i < ap.m_data.numAttributes(); i++) {
                System.out.println(ap.m_data.attribute(i).name());
            }

            jf.setSize(500, 300);
            jf.getContentPane().setLayout(new BorderLayout());
            jf.getContentPane().add(ap, BorderLayout.CENTER);
            jf.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
            jf.setVisible(true);
        } else {
            System.out.println("Usage: java AttributeVisualizationPanel" + " [arff file] [index of attribute]");
        }
    }
}

/*
 * t =(int) Math.ceil((float)(
 * (m_data.instance(k).value(m_attribIndex)-m_as.numericStats.min) / barRange));
 * 1. This equation gives a value between (i-1)+smallfraction and i if the
 * attribute m_attribIndex for the current instances lies in the ith interval.
 * We then increment the value of our i-1th field of our histogram/barplot
 * array. If, for example, barRange=3 then, apart from the 1st interval, each
 * interval i has values in the range (minValue+3*i-1, minValue+3*i]. The 1st
 * interval has range [minValue, minValue+i]. Hence it can be seen in the code
 * we specifically handle t=0 separately.
 */

/**
 * (barWidth==1)?x+barWidth*m_histBarClassCounts.length-1 :
 * x+barWidth*m_histBarClassCounts.length 2. In the case barWidth==1 we subtract
 * 1 otherwise the line become one pixel longer than the actual size of the
 * histogram
 */