/*
* 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 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;
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;
/**
* 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: 7587 $
*/
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 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("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(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()];
//[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.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 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 {
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;
}
double histClassCounts[][] =
new double[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] += 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(int i=0; i<histClassCounts.length; i++) {
double 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_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) /
(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((
(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 "<nominal value> [<nominal value count>]"
* if displaying a bar plot and mouse is on some bar.
* If displaying histogram then it
* <li>returns "count <br> [<bars Range>]" if mouse is
* on the first bar. </li>
* <li>returns "count <br> (<bar's Range>]" 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.nominalWeights!=null) { //if current attrib is nominal
float intervalWidth = this.getWidth() / (float)m_as.nominalWeights.length;
double 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.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;
//initializing y to point to (from top) the start of the bar
y = (int) (this.getHeight()-Math.round(m_as.nominalWeights[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.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)) {
double 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()-(double)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
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;
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 ("<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) //handle case temp!=0
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
*/
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.nominalWeights != null &&
(m_histBarClassCounts!=null || m_histBarCounts!=null) ) {
double 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.
double 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()-(double)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 = 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(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(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 = (int) (this.getHeight()-Math.round(m_histBarCounts[i]*heightRatio));
g.fillRect(x, y, barWidth,
(int) 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(Utils.doubleToString(m_histBarCounts[i], 1)) <
intervalWidth)
g.drawString(Utils.doubleToString(m_histBarCounts[i], 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;
float 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).
double 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 = sum + m_histBarClassCounts[i].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(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 = (int) (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,
(int) Math.round(m_histBarCounts[i]*heightRatio));
else if((m_histBarCounts[i]*heightRatio)>0)
g.drawLine(x, y,
x, (int) (y+Math.round(m_histBarCounts[i]*heightRatio)));
if(m_fm.stringWidth(" "+Utils.doubleToString(m_histBarCounts[i], 1)) <
barWidth)
g.drawString(" "+Utils.doubleToString(m_histBarCounts[i], 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( jf.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
*/