/* * 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:
* intervalWidth = Max(1, 3.49*Std.Dev*numInstances^(1/3)) * Then the number of intervals is calculated by:
* intervals = max(1, Math.round(Range/intervalWidth); * * @author Ashraf M. Kibriya (amk14@cs.waikato.ac.nz) * @version $Revision: 5721 $ */ 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; /** 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:
* intervalWidth = Max(1, 3.49*Std.Dev*numInstances^(1/3)) * And the number of intervals by:
* 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.
* 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:
* m_histBarClassCounts[nominalValue][classValue+1]. * For numeric attributes the format is:
* m_histBarClassCounts[interval][classValues+1],
* 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= 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; } } /** * 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 int value */ public int getColoringIndex() { return m_classIndex; //m_colorAttrib.getSelectedIndex(); } /** * Set the coloring (class) index for the plot * * @param ci an int 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; 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; im_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 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 0) { m_maxValue = m_as.nominalWeights[0]; for(int i=0; im_maxValue) m_maxValue = m_as.nominalWeights[i]; } } else { m_maxValue = 0; } for(int k=0; k= 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;jm_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 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; km_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; ireturns "count <br> [<bars Range>]" if mouse is * on the first bar. *
  • returns "count <br> (<bar's Range>]" if mouse is * on some bar other than the first one.
  • * 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= 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
    " + Utils.doubleToString(sum, 3) + "
    "+ "["+Utils.doubleToString(bar+m_barRange*temp,3)+ ", "+Utils.doubleToString((bar+m_barRange*(temp+1)),3)+ "]"+"
    "); } else if( temp < m_histBarClassCounts.length ) { //handle case temp!=0 double sum=0; for(int k=0; k
    " + Utils.doubleToString(sum, 3) + "
    ("+ Utils.doubleToString(bar+m_barRange*temp,3)+", "+ Utils.doubleToString((bar+m_barRange*(temp+1)),3)+ "]
    "); } } } 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 ("
    "+ m_histBarCounts[0]+"
    "+ "["+Utils.doubleToString(bar+m_barRange*temp,3)+", "+ Utils.doubleToString((bar+m_barRange*(temp+1)),3)+ "]"+ "
    "); else if(temp < m_histBarCounts.length) //handle case temp!=0 return ("
    "+ m_histBarCounts[temp]+"
    "+ "("+Utils.doubleToString(bar+m_barRange*temp,3)+", "+ Utils.doubleToString((bar+m_barRange*(temp+1)),3)+ "]"+ "
    "); } } } 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; i5) 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=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; i1) 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)) 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; i1) 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