/*
 *    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.
 */

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

package weka.gui;

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

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 javax.swing.JComboBox;
import javax.swing.JFrame;

/**
 * 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: 7589 $
 */
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.nominalCounts[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 int 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.nominalCounts[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 int 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.
   */
//  int m_histBarClassCounts[][];
    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 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 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 FastVector m_colorList = new FastVector();
  
  /** 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() {
      public void componentResized(ComponentEvent ce) {
        if(m_data!=null) {
          // calcGraph();
        }
      }
    });
    
    m_colorAttrib = new JComboBox();
    m_colorAttrib.addItemListener( new ItemListener() {
      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(Messages.getInstance().getString("AttributeVisualizationPanel_SetInstances_ColorAttrib_Text_First"));
      for(int i=0; i<m_data.numAttributes(); i++) {
	String type = "";
	switch (m_data.attribute(i).type()) {
	  case Attribute.NOMINAL:
	    type = Messages.getInstance().getString("AttributeVisualizationPanel_SetInstances_AttributeNOMINAL_Text");
	    break;
	  case Attribute.NUMERIC:
	    type = Messages.getInstance().getString("AttributeVisualizationPanel_SetInstances_AttributeNUMERIC_Text");
	    break;
	  case Attribute.STRING:
	    type = Messages.getInstance().getString("AttributeVisualizationPanel_SetInstances_AttributeSTRING_Text");
	    break;
	  case Attribute.DATE:
	    type = Messages.getInstance().getString("AttributeVisualizationPanel_SetInstances_AttributeDATE_Text");
	    break;
	  case Attribute.RELATIONAL:
	    type = Messages.getInstance().getString("AttributeVisualizationPanel_SetInstances_AttributeRELATIONAL_Text");
	    break;
	  default:
	    type = Messages.getInstance().getString("AttributeVisualizationPanel_SetInstances_AttributeDEFAULT_Text");
	}
        m_colorAttrib.addItem(new String(Messages.getInstance().getString("AttributeVisualizationPanel_SetInstances_ColorAttrib_Text_Second") + 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.nominalCounts!=null) {
        m_hc = new BarCalc(panelWidth, panelHeight);
        m_hc.setPriority(m_hc.MIN_PRIORITY);
        m_hc.start();
      }
      else if(m_as.numericStats!=null) {
        m_hc = new HistCalc();
        m_hc.setPriority(m_hc.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 int m_panelWidth;
    private int m_panelHeight;
    
    public BarCalc(int panelWidth, int panelHeight) {
      m_panelWidth = panelWidth;
      m_panelHeight = panelHeight;
    }
    
    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()];
          /*int histClassCounts[][];
          histClassCounts=new int[m_data.attribute(m_attribIndex).numValues()]
                                 [m_data.attribute(m_classIndex).numValues()+1]; */
          
          if (m_as.nominalCounts.length > 0) {
            m_maxValue = m_as.nominalCounts[0];
            for(int i=0; i<m_data.attribute(m_attribIndex).numValues(); i++) {
              if(m_as.nominalCounts[i]>m_maxValue)
        	m_maxValue = m_as.nominalCounts[i];
            }
          }
          else {
            m_maxValue = 0;
          }
          
          if(m_colorList.size()==0)
            m_colorList.addElement(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.addElement(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 (int z = 0; z < tempClassCounts.length; z++) {
                    if (tempClassCounts[z] > 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 (int z = 0; z < tempClassCounts.length; z++) {
              if (tempClassCounts[z] > 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 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))
              if(m_data.instance(k).isMissing(m_classIndex))
                histClassCounts[(int)m_data.instance(k).value(m_attribIndex)]
                               [0]++;
              else
                histClassCounts[(int)m_data.instance(k).value(m_attribIndex)]
                              [(int)m_data.instance(k).value(m_classIndex)+1]++;
          } */
          
          //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.nominalCounts[i]);
          //}
          
          m_threadRun=false;
          m_doneCurrentAttribute = true;
          m_displayCurrentAttribute = true;
          m_histBarClassCounts = histClassCounts;
          // m_histBarClassCounts = histClassCounts;
          //Image tmpImg = new BufferedImage(getWidth(), getHeight(),
          //                                 BufferedImage.TYPE_INT_RGB);
          //drawGraph( tmpImg.getGraphics() );
          //img = tmpImg;
          AttributeVisualizationPanel.this.repaint();
        }
        else {
          int histCounts[];
          histCounts  = new int[m_data.attribute(m_attribIndex).numValues()];
          
          if (m_as.nominalCounts.length > 0) {
            m_maxValue = m_as.nominalCounts[0];
            for(int i=0; i<m_data.attribute(m_attribIndex).numValues(); i++) {
              if(m_as.nominalCounts[i]>m_maxValue)
        	m_maxValue = m_as.nominalCounts[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_threadRun=false;
          m_histBarCounts = histCounts;
          m_doneCurrentAttribute = true;
          m_displayCurrentAttribute = true;
          //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 {
    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)//if width is too small then use 1 and forget padding
              intervals = 1;
          }
          int histClassCounts[][]  =
                          new int[intervals]
                                 [m_data.attribute(m_classIndex).numValues()+1];
          
          double barRange   = (m_as.numericStats.max - m_as.numericStats.min) /
                              (double)histClassCounts.length;
          
          m_maxValue = 0;
          
          if(m_colorList.size()==0)
            m_colorList.addElement(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.addElement(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]++;
                  else
                    histClassCounts[t][(int)m_data.instance(k).value(m_classIndex)+1]++;
                  //if(histCounts[t]>m_maxValue)
                  //  m_maxValue = histCounts[t];
                }
                else {
                  if(m_data.instance(k).isMissing(m_classIndex))
                    histClassCounts[t-1][0]++;
                  else
                    histClassCounts[t-1][(int)m_data.instance(k).value(m_classIndex)+1]++;
                  //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(int i=0; i<histClassCounts.length; i++) {
            int sum=0;
            for(int j=0; j<histClassCounts[i].length; j++)
              sum = sum+histClassCounts[i][j];
            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_histBarClassCounts = histClassCounts;
          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;
          }
          
          int histCounts[]  = new int[intervals];
          double barRange   = (m_as.numericStats.max - m_as.numericStats.min) /
                              (double)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]++;
                if(histCounts[t]>m_maxValue)
                  m_maxValue = histCounts[t];
              }
              else {
                histCounts[t-1]++;
                if(histCounts[t-1]>m_maxValue)
                  m_maxValue = histCounts[t-1];
              }
            }
            catch(ArrayIndexOutOfBoundsException ae) {
              ae.printStackTrace();
              System.out.println(Messages.getInstance().getString("AttributeVisualizationPanel_HistCalc_Run_ArrayIndexOutOfBoundsException_Text_First") + (t)+
              Messages.getInstance().getString("AttributeVisualizationPanel_HistCalc_Run_ArrayIndexOutOfBoundsException_Text_Second") + barRange+
              Messages.getInstance().getString("AttributeVisualizationPanel_HistCalc_Run_ArrayIndexOutOfBoundsException_Text_Third") + histCounts.length+
              Messages.getInstance().getString("AttributeVisualizationPanel_HistCalc_Run_ArrayIndexOutOfBoundsException_Text_Fourth") + m_data.instance(k).value(m_attribIndex)+
              Messages.getInstance().getString("AttributeVisualizationPanel_HistCalc_Run_ArrayIndexOutOfBoundsException_Text_Fifth") + m_as.numericStats.min+
              Messages.getInstance().getString("AttributeVisualizationPanel_HistCalc_Run_ArrayIndexOutOfBoundsException_Text_Sixth") +
              (m_data.instance(k).value(m_attribIndex)-m_as.numericStats.min)+
              Messages.getInstance().getString("AttributeVisualizationPanel_HistCalc_Run_ArrayIndexOutOfBoundsException_Text_Seventh") +
              (float)((m_data.instance(k).value(m_attribIndex) -
              m_as.numericStats.min)/barRange)+
              Messages.getInstance().getString("AttributeVisualizationPanel_HistCalc_Run_ArrayIndexOutOfBoundsException_Text_Eighth") +
              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
   */
  public String getToolTipText(MouseEvent ev) {
    
    if(m_as!=null && m_as.nominalCounts!=null) { //if current attrib is nominal
      
      float intervalWidth = this.getWidth() / (float)m_as.nominalCounts.length;
      float heightRatio;      
      int barWidth, x=0, y=0;
      
      //if intervalWidth is at least six then bar width is 80% of intervalwidth
      if(intervalWidth>5) //the rest is padding
        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.nominalCounts.length*barWidth+
                           (int)( (Math.floor(intervalWidth*0.2F))<1 ? 
                                   1:(Math.floor(intervalWidth*0.2F)) ) * 
                           m_as.nominalCounts.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.nominalCounts.length*barWidth + 
                                 (int)( (Math.floor(intervalWidth*0.2F))<1 ? 
                                        1:(Math.floor(intervalWidth*0.2F)) ) * 
                                 m_as.nominalCounts.length) ) / 2;
      }
        
      for(int i=0; i<m_as.nominalCounts.length; i++) {        
        heightRatio = (this.getHeight()-(float)m_fm.getHeight())/m_maxValue;
        //initializing y to point to (from top) the start of the bar
        y = this.getHeight()-Math.round(m_as.nominalCounts[i]*heightRatio);
        
        //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.nominalCounts[i]*heightRatio) )
          return(m_data.attribute(m_attribIndex).value(i)+
                 " ["+m_as.nominalCounts[i]+"]");
        //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)) {

      float heightRatio, intervalWidth;
      int x=0, y=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;
        
        heightRatio = (this.getHeight()-(float)m_fm.getHeight())/m_maxValue;
        
        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
            int 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 (Messages.getInstance().getString("AttributeVisualizationPanel_GetToolTipText_Text_First") + sum+
            		Messages.getInstance().getString("AttributeVisualizationPanel_GetToolTipText_Text_Second") + Utils.doubleToString(bar+m_barRange*temp,3)+
                    ", "+Utils.doubleToString((bar+m_barRange*(temp+1)),3)+
                    Messages.getInstance().getString("AttributeVisualizationPanel_GetToolTipText_Text_Fourth"));
          }
          else if( temp < m_histBarClassCounts.length ) { //handle case temp!=0
            int 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 (Messages.getInstance().getString("AttributeVisualizationPanel_GetToolTipText_Text_Fifth") + sum + Messages.getInstance().getString("AttributeVisualizationPanel_GetToolTipText_Text_Sixth") +
                    Utils.doubleToString(bar+m_barRange*temp,3)+", "+
                    Utils.doubleToString((bar+m_barRange*(temp+1)),3)+
                    Messages.getInstance().getString("AttributeVisualizationPanel_GetToolTipText_Text_Eighth"));
          }
        }
      }
      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;
        
        heightRatio = (this.getHeight()-(float)m_fm.getHeight())/m_maxValue;
        
        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) //handle special case temp==0. see footnote 1.
            return (Messages.getInstance().getString("AttributeVisualizationPanel_GetToolTipText_Text_Nineth") +
                    m_histBarCounts[0]+Messages.getInstance().getString("AttributeVisualizationPanel_GetToolTipText_Text_Texth") + Utils.doubleToString(bar+m_barRange*temp,3)+", "+
                    Utils.doubleToString((bar+m_barRange*(temp+1)),3)+
                    Messages.getInstance().getString("AttributeVisualizationPanel_GetToolTipText_Text_Twelveth"));
          else if(temp < m_histBarCounts.length) //handle case temp!=0
            return (Messages.getInstance().getString("AttributeVisualizationPanel_GetToolTipText_Text_Thirteenth") +
                    m_histBarCounts[temp]+ Messages.getInstance().getString("AttributeVisualizationPanel_GetToolTipText_Text_Fourteenth") + Utils.doubleToString(bar+m_barRange*temp,3)+", "+
                    Utils.doubleToString((bar+m_barRange*(temp+1)),3)+
                    Messages.getInstance().getString("AttributeVisualizationPanel_GetToolTipText_Text_Sixteenth"));
        }
      }
    }
    return PrintableComponent.getToolTipText(m_Printer);
  }
  
  
  /**
   * Paints this component
   *
   * @param g The graphics object for this component
   */
  public void paintComponent(Graphics g) {
    g.clearRect(0,0,this.getWidth(), this.getHeight());
    
    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.nominalCounts != null && 
           (m_histBarClassCounts!=null || m_histBarCounts!=null) ) {
          float heightRatio, intervalWidth;
          int x=0, y=0, barHeight, 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.
            int sum=0;
            for(int i=0; i<m_histBarClassCounts.length; i++) {
              //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()-(float)m_fm.getHeight() - 
                              buttonHeight ) / m_maxValue;              
              y=this.getHeight();
              if (m_histBarClassCounts[i] != null) {
                for(int j=0; j<m_histBarClassCounts[i].numAttributes(); j++) {
                  sum = (int) (sum + m_histBarClassCounts[i].value(j));
                  y = (int) (y-Math.round(m_histBarClassCounts[i].value(j)*heightRatio));
                  //selecting the colour corresponding to the current class.
                  g.setColor( (Color)m_colorList.elementAt(j) );
                  g.fillRect(x, y, barWidth, 
                      (int) Math.round(m_histBarClassCounts[i].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(Integer.toString(sum))<intervalWidth)
                g.drawString(Integer.toString(sum), 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(int i=0; i<m_histBarCounts.length; i++) {
              //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 = this.getHeight()-Math.round(m_histBarCounts[i]*heightRatio);
              g.fillRect(x, y, barWidth, 
                         Math.round(m_histBarCounts[i]*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(Integer.toString(m_histBarCounts[i])) < 
                                    intervalWidth)
                g.drawString(Integer.toString(m_histBarCounts[i]), 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)) {

          float heightRatio, intervalWidth;
          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(int i=0; i<m_histBarClassCounts.length; i++) {
              if (m_histBarClassCounts[i] != 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).
                int sum = 0;
                for(int j=0; j<m_histBarClassCounts[i].numValues(); j++) {
                  y = (int) (y-Math.round(m_histBarClassCounts[i].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( (Color)m_colorList.elementAt(m_histBarClassCounts[i].index(j)) );
                  //drawing the bar if its width is greater than 1
                  if(barWidth>1)
                    g.fillRect(x, y, 
                        barWidth, 
                        (int) Math.round(m_histBarClassCounts[i].valueSparse(j)*heightRatio));
                  //otherwise drawing a line
                  else if((m_histBarClassCounts[i].valueSparse(j)*heightRatio)>0)
                    g.drawLine(x, y, x, 
                        (int) (y+Math.round(m_histBarClassCounts[i].valueSparse(j)*heightRatio)));
                  g.setColor(Color.black);
                  sum = (int) (sum + m_histBarClassCounts[i].valueSparse(j));
                }
                //Drawing bar count on the top of the bar if it is < barWidth
                if(m_fm.stringWidth(" "+Integer.toString(sum))<barWidth)
                  g.drawString(" "+Integer.toString(sum), 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(int i=0; i<m_histBarCounts.length; i++) {
              //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 = this.getHeight() - 
                  Math.round(m_histBarCounts[i]*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, 
                           Math.round(m_histBarCounts[i]*heightRatio));
              else if((m_histBarCounts[i]*heightRatio)>0)
                g.drawLine(x, y, 
                           x, y+Math.round(m_histBarCounts[i]*heightRatio));
              if(m_fm.stringWidth(" "+Integer.toString(m_histBarCounts[i])) < 
                    barWidth)
                g.drawString(" "+Integer.toString(m_histBarCounts[i]), 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(Messages.getInstance().getString("AttributeVisualizationPanel_PaintComponent_G_DrawString_Text_First"),
          this.getWidth()/2 - m_fm.
          stringWidth(Messages.getInstance().getString("AttributeVisualizationPanel_PaintComponent_StringWidth_Text_First"))/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(Messages.getInstance().getString("AttributeVisualizationPanel_PaintComponent_G_DrawString_Text_Second"),
        this.getWidth()/2 - m_fm.stringWidth(Messages.getInstance().getString("AttributeVisualizationPanel_PaintComponent_StringWidth_Text_Second"))/2,
        this.getHeight()/2-m_fm.getHeight()/2);
      }
      else if (!m_displayCurrentAttribute) {
        g.clearRect(0, 0, this.getWidth(), this.getHeight());
        g.drawString(Messages.getInstance().getString("AttributeVisualizationPanel_PaintComponent_G_DrawString_Text_Third"),
        this.getWidth()/2 - m_fm.stringWidth(Messages.getInstance().getString("AttributeVisualizationPanel_PaintComponent_StringWidth_Text_Third"))/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(Messages.getInstance().getString("AttributeVisualizationPanel_Main_JFrame_Text"));
      AttributeVisualizationPanel ap = new AttributeVisualizationPanel();
      try {
        Instances ins = new Instances( new FileReader(args[0]) );
        ap.setInstances(ins);
        System.out.println(Messages.getInstance().getString("AttributeVisualizationPanel_Main_Text_First") + args[0]+
                           Messages.getInstance().getString("AttributeVisualizationPanel_Main_Text_Second") + ap.m_data.relationName()+
                           Messages.getInstance().getString("AttributeVisualizationPanel_Main_Text_Third") + ap.m_data.numAttributes());
        ap.setAttribute( Integer.parseInt(args[1]) );
      }
      catch(Exception ex) { ex.printStackTrace(); System.exit(-1); }
      System.out.println(Messages.getInstance().getString("AttributeVisualizationPanel_Main_Text_Fourth"));
      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( jf.EXIT_ON_CLOSE );
      jf.setVisible(true);
    }
    else
      System.out.println(Messages.getInstance().getString("AttributeVisualizationPanel_Main_Text_Fifth"));
  }
}


/*
 * 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
 */
