/*
* 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.
*/
/*
* AttributeSummaryPanel.java
* Copyright (C) 2003 Ashraf M. Kibriya
*
*/
package weka.gui;
import java.io.FileReader;
import java.util.Random;
import java.awt.BorderLayout;
import java.awt.Graphics;
import java.awt.Font;
import java.awt.FontMetrics;
import java.awt.event.MouseEvent;
import javax.swing.JPanel;
import javax.swing.JFrame;
import javax.swing.JButton;
import weka.core.Instances;
import weka.core.AttributeStats;
import weka.core.Utils;
/**
* 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 shows the spread
* of the attribute using a scatter plot in which the
* X-axis correspond to the values of the attribute and
* a random noise is added to the Y-axis.
*
* @author Ashraf M. Kibriya (amk14@cs.waikato.ac.nz)
* @version $Revision: 1.1.1.1 $
*/
public class AttributeVisualizationPanel extends JPanel {
Instances m_data;
AttributeStats as;
int attribIndex, maxValue;
FontMetrics fm;
public AttributeVisualizationPanel() {
this.setFont( new Font("Default", Font.PLAIN, 8) );
fm = this.getFontMetrics( this.getFont() );
this.setToolTipText("");
}
/**
* Sets the instances for use
*
* @param newins a set of Instances
*/
public void setInstances(Instances newins) {
m_data = newins;
as=null;
this.repaint();
}
/**
* Tells the panel which attribute to visualize.
*
* @param index The index of the attribute
*/
public void setAttribute(int index) {
attribIndex=index;
as = m_data.attributeStats(attribIndex);
if(as.nominalCounts!=null) {
maxValue = as.nominalCounts[0];
for(int i=0; i<m_data.attribute(attribIndex).numValues(); i++) {
if(as.nominalCounts[i]>maxValue)
maxValue = as.nominalCounts[i];
}
}
this.repaint();
}
/**
* Returns "<nominal value> [<nominal value count>]"
* in case if displaying a bar plot and mouse is on some bar.
* Otherwise returns ""
*
* @param ev The mouse event
*/
public String getToolTipText(MouseEvent ev) {
if(as!=null && as.nominalCounts!=null) {
float intervalWidth = this.getWidth()/(float)as.nominalCounts.length, heightRatio;
int barWidth, x=0, y=0;
if(intervalWidth>5)
barWidth = (int)Math.floor(intervalWidth*0.8F);
else
barWidth = 1;
x = x + (int)( (Math.floor(intervalWidth*0.1F))<1 ? 1:(Math.floor(intervalWidth*0.1F)) );
if( this.getWidth() -
(x + as.nominalCounts.length*barWidth
+(int)( (Math.floor(intervalWidth*0.2F))<1 ? 1:(Math.floor(intervalWidth*0.2F)) )*as.nominalCounts.length) > 5 )
x += (this.getWidth() -
(x + as.nominalCounts.length*barWidth +
(int)( (Math.floor(intervalWidth*0.2F))<1 ? 1:(Math.floor(intervalWidth*0.2F)) )*as.nominalCounts.length))/2;
for(int i=0; i<as.nominalCounts.length; i++) {
heightRatio = (this.getHeight()-(float)fm.getHeight())/maxValue;
y = this.getHeight()-Math.round(as.nominalCounts[i]*heightRatio);
if(ev.getX()>=x && ev.getX()<=x+barWidth
&& ev.getY()>=this.getHeight()-Math.round(as.nominalCounts[i]*heightRatio) )
return(m_data.attribute(attribIndex).value(i)+" ["+as.nominalCounts[i]+"]");
x = x+barWidth+(int)( (Math.floor(intervalWidth*0.2F))<1 ? 1:(Math.floor(intervalWidth*0.2F)) );
}
return "";
}
return "";
}
/**
* 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(as!=null) {
if(as.nominalCounts != null) {
float heightRatio, intervalWidth;
int x=0, y=0, barHeight, barWidth;
intervalWidth = (this.getWidth()/(float)as.nominalCounts.length);
if(intervalWidth>5)
barWidth = (int)Math.floor(intervalWidth*0.8F);
else
barWidth = 1;
x = x + (int)( (Math.floor(intervalWidth*0.1F))<1 ? 1:(Math.floor(intervalWidth*0.1F)) );
if( this.getWidth() -
(x + as.nominalCounts.length*barWidth
+(int)( (Math.floor(intervalWidth*0.2F))<1 ? 1:(Math.floor(intervalWidth*0.2F)) )*as.nominalCounts.length) > 5 )
x += (this.getWidth() -
(x + as.nominalCounts.length*barWidth +
(int)( (Math.floor(intervalWidth*0.2F))<1 ? 1:(Math.floor(intervalWidth*0.2F)) )*as.nominalCounts.length))/2;
for(int i=0; i<as.nominalCounts.length; i++) {
heightRatio = (this.getHeight()-(float)fm.getHeight())/maxValue;
y = this.getHeight()-Math.round(as.nominalCounts[i]*heightRatio);
g.fillRect(x, y, barWidth, Math.round(as.nominalCounts[i]*heightRatio));
if(fm.stringWidth(Integer.toString(as.nominalCounts[i]))<intervalWidth)
g.drawString(Integer.toString(as.nominalCounts[i]), x, y-1);
x = x+barWidth+(int)( (Math.floor(intervalWidth*0.2F))<1 ? 1:(Math.floor(intervalWidth*0.2F)) );
}
} else if(as.numericStats != null) {
float widthRatio;
int x, y;
Random rnd = new Random();
widthRatio = (float)((this.getWidth()-10)/(as.numericStats.max-as.numericStats.min));
for(int i=0; i<m_data.numInstances(); i++) {
if(m_data.instance(i).isMissing(attribIndex))
continue;
x = (int)Math.round((m_data.instance(i).value(attribIndex)-as.numericStats.min)*widthRatio)+5;
y = rnd.nextInt( this.getHeight()-20 );
if(m_data.numInstances()<1500)
g.drawOval(x,y,1,1);
else
g.drawLine(x,y,x,y);
}
g.drawLine(5, this.getHeight()-18, this.getWidth()-5, this.getHeight()-18); //axis line
g.drawLine(5, this.getHeight()-17, 5, this.getHeight()-12); //minimum line
g.drawString(Utils.doubleToString(as.numericStats.min, 2),
5,
this.getHeight()-12+fm.getHeight()); //minimum line
g.drawLine(5+(int)((as.numericStats.max-as.numericStats.min)*widthRatio/2),
this.getHeight()-17,
5+(int)((as.numericStats.max-as.numericStats.min)*widthRatio/2),
this.getHeight()-12); //median line
g.drawString(Utils.doubleToString(as.numericStats.max/2+as.numericStats.min/2, 2),
5+(int)((as.numericStats.max-as.numericStats.min)*widthRatio/2)
-fm.stringWidth(Utils.doubleToString(as.numericStats.max/2+as.numericStats.min/2, 2))/2,
this.getHeight()-12+fm.getHeight());
g.drawLine(this.getWidth()-5, this.getHeight()-17, this.getWidth()-5, this.getHeight()-12); //maximum line
g.drawString(Utils.doubleToString(as.numericStats.max, 2),
this.getWidth()-5-fm.stringWidth(Utils.doubleToString(as.numericStats.max, 2)),
this.getHeight()-12+fm.getHeight());
}
}
}
/**
* 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);
ap.setAttribute( Integer.parseInt(args[1]) );
}
catch(Exception ex) { ex.printStackTrace(); System.exit(-1); }
System.out.println("Loaded: "+args[0]+"\nRelation: "+ap.m_data.relationName()+"\nAttributes: "+ap.m_data.numAttributes());
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.show();
}
else
System.out.println("Usage: java AttributeVisualizationPanel [arff file] [index of attribute]");
}
}