All Downloads are FREE. Search and download functionalities are using the official Maven repository.

weka.gui.AttributeVisualizationPanel Maven / Gradle / Ivy

Go to download

The Waikato Environment for Knowledge Analysis (WEKA), a machine learning workbench. This version represents the developer version, the "bleeding edge" of development, you could say. New functionality gets added to this version.

There is a newer version: 3.9.6
Show newest version
/*
 *   This program is free software: you can redistribute it and/or modify
 *   it under the terms of the GNU General Public License as published by
 *   the Free Software Foundation, either version 3 of the License, or
 *   (at your option) any later version.
 *
 *   This program is distributed in the hope that it will be useful,
 *   but WITHOUT ANY WARRANTY; without even the implied warranty of
 *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *   GNU General Public License for more details.
 *
 *   You should have received a copy of the GNU General Public License
 *   along with this program.  If not, see .
 */

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

package weka.gui;

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

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

/**
 * Creates a panel that shows a visualization of an attribute in a dataset. For
 * nominal attribute it shows a bar plot, with each bar corresponding to each
 * nominal value of the attribute with its height equal to the frequecy that
 * value appears in the dataset. For numeric attributes, it displays a
 * histogram. The width of an interval in the histogram is calculated using
 * Scott's(1979) method: 
* 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 ([email protected]) * @version $Revision: 13530 $ */ 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:
* 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 final FontMetrics m_fm; /** * Lock variable to synchronize the different threads running currently in * this class. There are two to three threads in this class, AWT paint thread * which is handled differently in paintComponent() which checks on * m_threadRun to determine if it can perform full paint or not, the second * thread is the main execution thread and the third is the one represented by * m_hc which we start when we want to calculate the internal fields for a bar * plot or a histogram. */ private final Integer m_locker = new Integer(1); // Image img; /** * Contains discrete colours for colouring of subbars of histograms and bar * plots when the class attribute is set and is nominal */ private final ArrayList m_colorList = new ArrayList(); /** default colour list */ private static final Color[] m_defaultColors = { Color.blue, Color.red, Color.cyan, new Color(75, 123, 130), Color.pink, Color.green, Color.orange, new Color(255, 0, 255), new Color(255, 0, 0), new Color(0, 255, 0), }; /** * Constructor - If used then the class will not show the class selection * combo box. */ public AttributeVisualizationPanel() { this(false); } /** * Constructor. * * @param showColouringOption - should be true if the class selection combo * box is to be displayed with the histogram/barplot, or false * otherwise. P.S: the combo box is always created it just won't be * shown if showColouringOption is false. */ public AttributeVisualizationPanel(boolean showColouringOption) { this.setFont(new Font("Default", Font.PLAIN, 9)); m_fm = this.getFontMetrics(this.getFont()); this.setToolTipText(""); FlowLayout fl = new FlowLayout(FlowLayout.LEFT); this.setLayout(fl); this.addComponentListener(new ComponentAdapter() { @Override public void componentResized(ComponentEvent ce) { if (m_data != null) { // calcGraph(); } } }); m_colorAttrib = new JComboBox(); m_colorAttrib.addItemListener(new ItemListener() { @Override public void itemStateChanged(ItemEvent ie) { if (ie.getStateChange() == ItemEvent.SELECTED) { m_classIndex = m_colorAttrib.getSelectedIndex() - 1; if (m_as != null) { setAttribute(m_attribIndex); } } } }); if (showColouringOption) { // m_colorAttrib.setVisible(false); this.add(m_colorAttrib); validate(); } } /** * Sets the instances for use * * @param newins a set of Instances */ public void setInstances(Instances newins) { m_attribIndex = 0; m_as = null; m_data = new Instances(newins); if (m_colorAttrib != null) { m_colorAttrib.removeAllItems(); m_colorAttrib.addItem("No class"); for (int i = 0; i < m_data.numAttributes(); i++) { String type = "(" + Attribute.typeToStringShort(m_data.attribute(i)) + ")"; m_colorAttrib.addItem(new String("Class: " + m_data.attribute(i).name() + " " + type)); } if (m_data.classIndex() >= 0) { m_colorAttrib.setSelectedIndex(m_data.classIndex() + 1); } else { m_colorAttrib.setSelectedIndex(m_data.numAttributes()); } // if (m_data.classIndex() >= 0) { // m_colorAttrib.setSelectedIndex(m_data.classIndex()); // } } if (m_data.classIndex() >= 0) { m_classIndex = m_data.classIndex(); } else { m_classIndex = m_data.numAttributes() - 1; } m_asCache = new AttributeStats[m_data.numAttributes()]; } /** * Returns the class selection combo box if the parent component wants to * place it in itself or in some component other than this component. */ public JComboBox getColorBox() { return m_colorAttrib; } /** * Get the coloring (class) index for the plot * * @return an 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; if (m_asCache[index] != null) { m_as = m_asCache[index]; } else { m_asCache[index] = m_data.attributeStats(index); m_as = m_asCache[index]; } // m_as = m_data.attributeStats(m_attribIndex); // m_classIndex = m_colorAttrib.getSelectedIndex(); } this.repaint(); // calcGraph(); } /** * Recalculates the barplot or histogram to display, required usually when the * attribute is changed or the component is resized. */ public void calcGraph(int panelWidth, int panelHeight) { synchronized (m_locker) { m_threadRun = true; if (m_as.nominalWeights != null) { m_hc = new BarCalc(panelWidth, panelHeight); m_hc.setPriority(Thread.MIN_PRIORITY); m_hc.start(); } else if (m_as.numericStats != null) { m_hc = new HistCalc(); m_hc.setPriority(Thread.MIN_PRIORITY); m_hc.start(); } else { m_histBarCounts = null; m_histBarClassCounts = null; m_doneCurrentAttribute = true; m_threadRun = false; this.repaint(); } } } /** * Internal class that calculates the barplot to display, in a separate * thread. In particular it initializes some of the crucial internal fields * required by paintComponent() to display the histogram for the current * attribute. These include: m_histBarCounts or m_histBarClassCounts, * m_maxValue and m_colorList. */ private class BarCalc extends Thread { private final int m_panelWidth; public BarCalc(int panelWidth, int panelHeight) { m_panelWidth = panelWidth; } @Override public void run() { synchronized (m_locker) { // there is no use doing/displaying anything if the resolution // of the panel is less than the number of values for this attribute if (m_data.attribute(m_attribIndex).numValues() > m_panelWidth) { m_histBarClassCounts = null; m_threadRun = false; m_doneCurrentAttribute = true; m_displayCurrentAttribute = false; AttributeVisualizationPanel.this.repaint(); return; } if ((m_classIndex >= 0) && (m_data.attribute(m_classIndex).isNominal())) { SparseInstance histClassCounts[]; histClassCounts = new SparseInstance[m_data.attribute(m_attribIndex) .numValues()]; // [m_data.attribute(m_classIndex).numValues()+1]; if (m_as.nominalWeights.length > 0) { m_maxValue = m_as.nominalWeights[0]; for (int i = 0; i < m_data.attribute(m_attribIndex).numValues(); i++) { if (m_as.nominalWeights[i] > m_maxValue) { m_maxValue = m_as.nominalWeights[i]; } } } else { m_maxValue = 0; } if (m_colorList.size() == 0) { m_colorList.add(Color.black); } for (int i = m_colorList.size(); i < m_data.attribute(m_classIndex) .numValues() + 1; i++) { Color pc = m_defaultColors[(i - 1) % 10]; int ija = (i - 1) / 10; ija *= 2; for (int j = 0; j < ija; j++) { pc = pc.darker(); } m_colorList.add(pc); } // first sort data on attribute values m_data.sort(m_attribIndex); double[] tempClassCounts = null; int tempAttValueIndex = -1; for (int k = 0; k < m_data.numInstances(); k++) { // System.out.println("attrib: "+ // m_data.instance(k).value(m_attribIndex)+ // " class: "+ // m_data.instance(k).value(m_classIndex)); if (!m_data.instance(k).isMissing(m_attribIndex)) { // check to see if we need to allocate some space here if (m_data.instance(k).value(m_attribIndex) != tempAttValueIndex) { if (tempClassCounts != null) { // set up the sparse instance for the previous bar (if any) int numNonZero = 0; for (double tempClassCount : tempClassCounts) { if (tempClassCount > 0) { numNonZero++; } } double[] nonZeroVals = new double[numNonZero]; int[] nonZeroIndices = new int[numNonZero]; int count = 0; for (int z = 0; z < tempClassCounts.length; z++) { if (tempClassCounts[z] > 0) { nonZeroVals[count] = tempClassCounts[z]; nonZeroIndices[count++] = z; } } SparseInstance tempS = new SparseInstance(1.0, nonZeroVals, nonZeroIndices, tempClassCounts.length); histClassCounts[tempAttValueIndex] = tempS; } tempClassCounts = new double[m_data.attribute(m_classIndex) .numValues() + 1]; tempAttValueIndex = (int) m_data.instance(k).value( m_attribIndex); /* * histClassCounts[(int)m_data.instance(k).value(m_attribIndex)] * = new double[m_data.attribute(m_classIndex).numValues()+1]; */ } if (m_data.instance(k).isMissing(m_classIndex)) { /* * histClassCounts[(int)m_data.instance(k).value(m_attribIndex)] * [0] += m_data.instance(k).weight(); */ tempClassCounts[0] += m_data.instance(k).weight(); } else { tempClassCounts[(int) m_data.instance(k).value(m_classIndex) + 1] += m_data .instance(k).weight(); /* * histClassCounts[(int)m_data.instance(k).value(m_attribIndex)] * [(int)m_data.instance(k).value(m_classIndex)+1] += * m_data.instance(k).weight(); */ } } } // set up sparse instance for last bar? if (tempClassCounts != null) { // set up the sparse instance for the previous bar (if any) int numNonZero = 0; for (double tempClassCount : tempClassCounts) { if (tempClassCount > 0) { numNonZero++; } } double[] nonZeroVals = new double[numNonZero]; int[] nonZeroIndices = new int[numNonZero]; int count = 0; for (int z = 0; z < tempClassCounts.length; z++) { if (tempClassCounts[z] > 0) { nonZeroVals[count] = tempClassCounts[z]; nonZeroIndices[count++] = z; } } SparseInstance tempS = new SparseInstance(1.0, nonZeroVals, nonZeroIndices, tempClassCounts.length); histClassCounts[tempAttValueIndex] = tempS; } // for(int i=0; i 0) { m_maxValue = m_as.nominalWeights[0]; for (int i = 0; i < m_data.attribute(m_attribIndex).numValues(); i++) { if (m_as.nominalWeights[i] > m_maxValue) { m_maxValue = m_as.nominalWeights[i]; } } } else { m_maxValue = 0; } for (int k = 0; k < m_data.numInstances(); k++) { if (!m_data.instance(k).isMissing(m_attribIndex)) { histCounts[(int) m_data.instance(k).value(m_attribIndex)] += m_data .instance(k).weight(); } } m_threadRun = false; m_displayCurrentAttribute = true; m_doneCurrentAttribute = true; m_histBarCounts = histCounts; // Image tmpImg = new BufferedImage(getWidth(), getHeight(), // BufferedImage.TYPE_INT_RGB); // drawGraph( tmpImg.getGraphics() ); // img = tmpImg; AttributeVisualizationPanel.this.repaint(); } } // end synchronized } // end run() } /** * Internal class that calculates the histogram to display, in a separate * thread. In particular it initializes some of the crucial internal fields * required by paintComponent() to display the histogram for the current * attribute. These include: m_histBarCounts or m_histBarClassCounts, * m_maxValue and m_colorList. */ private class HistCalc extends Thread { @Override public void run() { synchronized (m_locker) { if ((m_classIndex >= 0) && (m_data.attribute(m_classIndex).isNominal())) { int intervals; double intervalWidth = 0.0; // This uses the M.P.Wand's method to calculate the histogram's // interval width. See "Data-Based Choice of Histogram Bin Width", in // The American Statistician, Vol. 51, No. 1, Feb., 1997, pp. 59-64. // intervalWidth = Math.pow(6D/( -psi(2, // g21())*m_data.numInstances()), // 1/3D ); // This uses the Scott's method to calculate the histogram's interval // width. See "On optimal and data-based histograms". // See Biometrika, 66, 605-610 OR see the same paper mentioned above. intervalWidth = 3.49 * m_as.numericStats.stdDev * Math.pow(m_data.numInstances(), -1 / 3D); // The Math.max is introduced to remove the possibility of // intervals=0 and =NAN that can happen if respectively all the // numeric // values are the same or the interval width is evaluated to zero. intervals = Math.max( 1, (int) Math.round((m_as.numericStats.max - m_as.numericStats.min) / intervalWidth)); // System.out.println("Max: "+m_as.numericStats.max+ // " Min: "+m_as.numericStats.min+ // " stdDev: "+m_as.numericStats.stdDev+ // "intervalWidth: "+intervalWidth); // The number 4 below actually represents a padding of 3 pixels on // each side of the histogram, and is also reflected in other parts of // the code in the shape of numerical constants like "6" here. if (intervals > AttributeVisualizationPanel.this.getWidth()) { intervals = AttributeVisualizationPanel.this.getWidth() - 6; if (intervals < 1) { intervals = 1; } } double histClassCounts[][] = new double[intervals][m_data.attribute( m_classIndex).numValues() + 1]; double barRange = (m_as.numericStats.max - m_as.numericStats.min) / histClassCounts.length; m_maxValue = 0; if (m_colorList.size() == 0) { m_colorList.add(Color.black); } for (int i = m_colorList.size(); i < m_data.attribute(m_classIndex) .numValues() + 1; i++) { Color pc = m_defaultColors[(i - 1) % 10]; int ija = (i - 1) / 10; ija *= 2; for (int j = 0; j < ija; j++) { pc = pc.darker(); } m_colorList.add(pc); } for (int k = 0; k < m_data.numInstances(); k++) { int t = 0; // This holds the interval that the attibute value of the // new instance belongs to. try { if (!m_data.instance(k).isMissing(m_attribIndex)) { // 1. see footnote at the end of this file t = (int) Math.ceil((float) ((m_data.instance(k).value( m_attribIndex) - m_as.numericStats.min) / barRange)); if (t == 0) { if (m_data.instance(k).isMissing(m_classIndex)) { histClassCounts[t][0] += m_data.instance(k).weight(); } else { histClassCounts[t][(int) m_data.instance(k).value( m_classIndex) + 1] += m_data.instance(k).weight(); // if(histCounts[t]>m_maxValue) // m_maxValue = histCounts[t]; } } else { if (m_data.instance(k).isMissing(m_classIndex)) { histClassCounts[t - 1][0] += m_data.instance(k).weight(); } else { histClassCounts[t - 1][(int) m_data.instance(k).value( m_classIndex) + 1] += m_data.instance(k).weight(); // if(histCounts[t-1]>m_maxValue) // m_maxValue = histCounts[t-1]; } } } } catch (ArrayIndexOutOfBoundsException ae) { System.out .println("t:" + (t) + " barRange:" + barRange + " histLength:" + histClassCounts.length + " value:" + m_data.instance(k).value(m_attribIndex) + " min:" + m_as.numericStats.min + " sumResult:" + (m_data.instance(k).value(m_attribIndex) - m_as.numericStats.min) + " divideResult:" + (float) ((m_data.instance(k).value(m_attribIndex) - m_as.numericStats.min) / barRange) + " finalResult:" + Math .ceil((float) ((m_data.instance(k).value(m_attribIndex) - m_as.numericStats.min) / barRange))); } } for (double[] histClassCount : histClassCounts) { double sum = 0; for (double element : histClassCount) { sum = sum + element; } if (m_maxValue < sum) { m_maxValue = sum; } } // convert to sparse instances SparseInstance[] histClassCountsSparse = new SparseInstance[histClassCounts.length]; for (int i = 0; i < histClassCounts.length; i++) { int numSparseValues = 0; for (int j = 0; j < histClassCounts[i].length; j++) { if (histClassCounts[i][j] > 0) { numSparseValues++; } } double[] sparseValues = new double[numSparseValues]; int[] sparseIndices = new int[numSparseValues]; int count = 0; for (int j = 0; j < histClassCounts[i].length; j++) { if (histClassCounts[i][j] > 0) { sparseValues[count] = histClassCounts[i][j]; sparseIndices[count++] = j; } } SparseInstance tempS = new SparseInstance(1.0, sparseValues, sparseIndices, histClassCounts[i].length); histClassCountsSparse[i] = tempS; } m_histBarClassCounts = histClassCountsSparse; m_barRange = barRange; } else { // else if the class attribute is numeric or the class is not // set int intervals; double intervalWidth; // At the time of this coding the // possibility of datasets with zero instances // was being dealt with in the // PreProcessPanel of weka Explorer. // old method of calculating number of intervals // intervals = m_as.totalCount>10 ? // (int)(m_as.totalCount*0.1):(int)m_as.totalCount; // This uses the M.P.Wand's method to calculate the histogram's // interval width. See "Data-Based Choice of Histogram Bin Width", in // The American Statistician, Vol. 51, No. 1, Feb., 1997, pp. 59-64. // intervalWidth = Math.pow(6D/(-psi(2, g21())*m_data.numInstances() // ), // 1/3D ); // This uses the Scott's method to calculate the histogram's interval // width. See "On optimal and data-based histograms". // See Biometrika, 66, 605-610 OR see the same paper mentioned above. intervalWidth = 3.49 * m_as.numericStats.stdDev * Math.pow(m_data.numInstances(), -1 / 3D); // The Math.max is introduced to remove the possibility of // intervals=0 and =NAN that can happen if respectively all the // numeric // values are the same or the interval width is evaluated to zero. intervals = Math.max( 1, (int) Math.round((m_as.numericStats.max - m_as.numericStats.min) / intervalWidth)); // The number 4 below actually represents a padding of 3 pixels on // each side of the histogram, and is also reflected in other parts of // the code in the shape of numerical constants like "6" here. if (intervals > AttributeVisualizationPanel.this.getWidth()) { intervals = AttributeVisualizationPanel.this.getWidth() - 6; if (intervals < 1) { intervals = 1; } } double[] histCounts = new double[intervals]; double barRange = (m_as.numericStats.max - m_as.numericStats.min) / histCounts.length; m_maxValue = 0; for (int k = 0; k < m_data.numInstances(); k++) { int t = 0; // This holds the interval to which the current // attribute's // value of this particular instance k belongs to. if (m_data.instance(k).isMissing(m_attribIndex)) { continue; // ignore missing values } try { // 1. see footnote at the end of this file t = (int) Math.ceil((float) ((m_data.instance(k).value( m_attribIndex) - m_as.numericStats.min) / barRange)); if (t == 0) { histCounts[t] += m_data.instance(k).weight(); if (histCounts[t] > m_maxValue) { m_maxValue = histCounts[t]; } } else { histCounts[t - 1] += m_data.instance(k).weight(); if (histCounts[t - 1] > m_maxValue) { m_maxValue = histCounts[t - 1]; } } } catch (ArrayIndexOutOfBoundsException ae) { ae.printStackTrace(); System.out .println("t:" + (t) + " barRange:" + barRange + " histLength:" + histCounts.length + " value:" + m_data.instance(k).value(m_attribIndex) + " min:" + m_as.numericStats.min + " sumResult:" + (m_data.instance(k).value(m_attribIndex) - m_as.numericStats.min) + " divideResult:" + (float) ((m_data.instance(k).value(m_attribIndex) - m_as.numericStats.min) / barRange) + " finalResult:" + Math .ceil((float) ((m_data.instance(k).value(m_attribIndex) - m_as.numericStats.min) / barRange))); } } m_histBarCounts = histCounts; m_barRange = barRange; } m_threadRun = false; m_displayCurrentAttribute = true; m_doneCurrentAttribute = true; // Image tmpImg = new BufferedImage(getWidth(), getHeight(), // BufferedImage.TYPE_INT_RGB); // drawGraph( tmpImg.getGraphics() ); // img = tmpImg; AttributeVisualizationPanel.this.repaint(); } } /**** * Code for M.P.Wand's method of histogram bin width selection. There is * some problem with it. It always comes up -ve value which is raised to the * power 1/3 and gives an NAN. private static final int M=400; private * double psi(int r, double g) { double val; * * double sum=0.0; for(int i=0; i * returns "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 */ @Override public String getToolTipText(MouseEvent ev) { if (m_as != null && m_as.nominalWeights != null) { // if current attrib is // nominal float intervalWidth = this.getWidth() / (float) m_as.nominalWeights.length; double heightRatio; int barWidth, x = 0; // if intervalWidth is at least six then bar width is 80% of intervalwidth if (intervalWidth > 5) { barWidth = (int) Math.floor(intervalWidth * 0.8F); } else { barWidth = 1; // Otherwise barwidth is 1 & padding would be at least 1. } // initializing x to maximum of 1 or 10% of interval width (i.e. half of // the padding which is 20% of interval width, as there is 10% on each // side of the bar) so that it points to the start of the 1st bar x = x + (int) ((Math.floor(intervalWidth * 0.1F)) < 1 ? 1 : (Math .floor(intervalWidth * 0.1F))); // Adding to x the appropriate value so that it points to the 1st bar of // our "centered" barplot. If subtracting barplots width from panel width // gives <=2 then the barplot is not centered. if (this.getWidth() - (m_as.nominalWeights.length * barWidth + (int) ((Math .floor(intervalWidth * 0.2F)) < 1 ? 1 : (Math .floor(intervalWidth * 0.2F))) * m_as.nominalWeights.length) > 2) { // The following amounts to adding to x the half of the area left after // subtracting from the components width the width of the whole barplot // (i.e. width of all the bars plus the width of all the bar paddings, // thereby equaling to the whole barplot), since our barplot is // centered. x += (this.getWidth() - (m_as.nominalWeights.length * barWidth + (int) ((Math .floor(intervalWidth * 0.2F)) < 1 ? 1 : (Math .floor(intervalWidth * 0.2F))) * m_as.nominalWeights.length)) / 2; } for (int i = 0; i < m_as.nominalWeights.length; i++) { heightRatio = (this.getHeight() - (double) m_fm.getHeight()) / m_maxValue; // if our mouse is on a bar then return the count of this bar in our // barplot if (ev.getX() >= x && ev.getX() <= x + barWidth && ev.getY() >= this.getHeight() - Math.round(m_as.nominalWeights[i] * heightRatio)) { return (m_data.attribute(m_attribIndex).value(i) + " [" + Utils.doubleToString(m_as.nominalWeights[i], 3) + "]"); } // otherwise advance x to next bar and check that. Add barwidth to x // and padding which is max(1, 20% of interval width) x = x + barWidth + (int) ((Math.floor(intervalWidth * 0.2F)) < 1 ? 1 : (Math .floor(intervalWidth * 0.2F))); } } else if (m_threadRun == false && // if attrib is numeric (m_histBarCounts != null || m_histBarClassCounts != null)) { int x = 0, barWidth; double bar = m_as.numericStats.min; // if the class attribute is set and it is nominal if ((m_classIndex >= 0) && (m_data.attribute(m_classIndex).isNominal())) { // there is 3 pixels of padding on each side of the histogram // the barwidth is 1 if after removing the padding its width is less // then the displayable width barWidth = ((this.getWidth() - 6) / m_histBarClassCounts.length) < 1 ? 1 : ((this.getWidth() - 6) / m_histBarClassCounts.length); // initializing x to 3 adding appropriate value to make it point to the // start of the 1st bar of our "centered" histogram. x = 3; if ((this.getWidth() - (x + m_histBarClassCounts.length * barWidth)) > 5) { x += (this.getWidth() - (x + m_histBarClassCounts.length * barWidth)) / 2; } if (ev.getX() - x >= 0) { // The temp holds the index of the current interval that we are // looking // at int temp = (int) ((ev.getX() - x) / (barWidth + 0.0000000001)); if (temp == 0) { // handle the special case temp==0. see footnote 1 double sum = 0; for (int k = 0; k < m_histBarClassCounts[0].numValues(); k++) { sum += m_histBarClassCounts[0].valueSparse(k); } // return the count of the interval mouse is pointing to plus // the range of values that fall into this interval return ("
    " + 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 < 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 ("
    " + 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; } if (ev.getX() - x >= 0) { // Temp holds the index of the current bar we are looking at. int temp = (int) ((ev.getX() - x) / (barWidth + 0.0000000001)); // return interval count as well as its range if (temp == 0) { return ("
    " + m_histBarCounts[0] + "
    " + "[" + Utils.doubleToString(bar + m_barRange * temp, 3) + ", " + Utils.doubleToString((bar + m_barRange * (temp + 1)), 3) + "]" + "
    "); } else if (temp < m_histBarCounts.length) { 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 */ @Override public void paintComponent(Graphics g) { g.setColor(Color.WHITE); g.fillRect(0, 0, this.getWidth(), this.getHeight()); g.setColor(Color.BLACK); if (m_as != null) { // If calculations have been done and histogram/barplot if (!m_doneCurrentAttribute && !m_threadRun) { calcGraph(this.getWidth(), this.getHeight()); } if (m_threadRun == false && m_displayCurrentAttribute) { // calculation // thread is not // running int buttonHeight = 0; if (m_colorAttrib != null) { buttonHeight = m_colorAttrib.getHeight() + m_colorAttrib.getLocation().y; } // if current attribute is nominal then draw barplot. if (m_as.nominalWeights != null && (m_histBarClassCounts != null || m_histBarCounts != null)) { double heightRatio, intervalWidth; int x = 0, y = 0, barWidth; // if the class attribute is set and is nominal then draw coloured // subbars for each bar if ((m_classIndex >= 0) && (m_data.attribute(m_classIndex).isNominal())) { intervalWidth = (this.getWidth() / (float) m_histBarClassCounts.length); // Barwidth is 80% of interval width.The remaining 20% is padding, // 10% on each side of the bar. If interval width is less then 5 the // 20% of that value is less than 1, in that case we use bar width // of // 1 and padding of 1 pixel on each side of the bar. if (intervalWidth > 5) { barWidth = (int) Math.floor(intervalWidth * 0.8F); } else { barWidth = 1; } // initializing x to 10% of interval width or to 1 if 10% is <1. // This // is essentially the LHS padding of the 1st bar. x = x + (int) ((Math.floor(intervalWidth * 0.1F)) < 1 ? 1 : (Math .floor(intervalWidth * 0.1F))); // Add appropriate value to x so that it starts at the 1st bar of // a "centered" barplot. if (this.getWidth() - (m_histBarClassCounts.length * barWidth + (int) ((Math .floor(intervalWidth * 0.2F)) < 1 ? 1 : (Math .floor(intervalWidth * 0.2F))) * m_histBarClassCounts.length) > 2) { // We take the width of all the bars and all the paddings (20% // of interval width), and subtract it from the width of the panel // to get the extra space that would be left after drawing. We // divide that space by 2 to get its mid-point and add that to our // x, thus making the whole bar plot drawn centered in our // component. x += (this.getWidth() - (m_histBarClassCounts.length * barWidth + (int) ((Math .floor(intervalWidth * 0.2F)) < 1 ? 1 : (Math .floor(intervalWidth * 0.2F))) * m_histBarClassCounts.length)) / 2; } // this holds the count of the bar and will be calculated by adding // up the counts of individual subbars. It is displayed at the top // of each bar. double sum = 0; for (SparseInstance m_histBarClassCount : m_histBarClassCounts) { // calculating the proportion of the components height compared to // the maxvalue in our attribute, also taking into account the // height of font to display bars count and the height of the // class // ComboBox. heightRatio = (this.getHeight() - (double) m_fm.getHeight() - buttonHeight) / m_maxValue; y = this.getHeight(); if (m_histBarClassCount != null) { for (int j = 0; j < m_histBarClassCount.numAttributes(); j++) { sum = sum + m_histBarClassCount.value(j); y = (int) (y - Math.round(m_histBarClassCount.value(j) * heightRatio)); // selecting the colour corresponding to the current class. g.setColor(m_colorList.get(j)); g.fillRect(x, y, barWidth, (int) Math .round(m_histBarClassCount.value(j) * heightRatio)); g.setColor(Color.black); } } // drawing the bar count at the top of the bar if it is less than // interval width. draw it 1px up to avoid touching the bar. if (m_fm.stringWidth(Utils.doubleToString(sum, 1)) < intervalWidth) { g.drawString(Utils.doubleToString(sum, 1), x, y - 1); } // advancing x to the next bar by adding bar width and padding // of both the bars (i.e. RHS padding of the bar just drawn and // LHS // padding of the new bar). x = x + barWidth + (int) ((Math.floor(intervalWidth * 0.2F)) < 1 ? 1 : (Math .floor(intervalWidth * 0.2F))); // reseting sum for the next bar. sum = 0; } } // else if class attribute is numeric or not set then draw black bars. else { intervalWidth = (this.getWidth() / (float) m_histBarCounts.length); // same as in the case of nominal class (see inside of if stmt // corresponding to the current else above). if (intervalWidth > 5) { barWidth = (int) Math.floor(intervalWidth * 0.8F); } else { barWidth = 1; } // same as in the case of nominal class (see inside of if stmt // corresponding to the current else above). x = x + (int) ((Math.floor(intervalWidth * 0.1F)) < 1 ? 1 : (Math .floor(intervalWidth * 0.1F))); // same as in the case of nominal class if (this.getWidth() - (m_histBarCounts.length * barWidth + (int) ((Math .floor(intervalWidth * 0.2F)) < 1 ? 1 : (Math .floor(intervalWidth * 0.2F))) * m_histBarCounts.length) > 2) { x += (this.getWidth() - (m_histBarCounts.length * barWidth + (int) ((Math .floor(intervalWidth * 0.2F)) < 1 ? 1 : (Math .floor(intervalWidth * 0.2F))) * m_histBarCounts.length)) / 2; } for (double m_histBarCount : m_histBarCounts) { // calculating the proportion of the height of the component // compared to the maxValue in our attribute. heightRatio = (this.getHeight() - (float) m_fm.getHeight() - buttonHeight) / m_maxValue; y = (int) (this.getHeight() - Math.round(m_histBarCount * heightRatio)); g.fillRect(x, y, barWidth, (int) Math.round(m_histBarCount * heightRatio)); // draw the bar count if it's width is smaller than intervalWidth. // draw it 1px above to avoid touching the bar. if (m_fm.stringWidth(Utils.doubleToString(m_histBarCount, 1)) < intervalWidth) { g.drawString(Utils.doubleToString(m_histBarCount, 1), x, y - 1); } // Advance x to the next bar by adding bar-width and padding // of the bars (RHS padding of current bar & LHS padding of next // bar). x = x + barWidth + (int) ((Math.floor(intervalWidth * 0.2F)) < 1 ? 1 : (Math .floor(intervalWidth * 0.2F))); } } } // <--end if m_as.nominalCount!=null // if the current attribute is numeric then draw a histogram. else if (m_as.numericStats != null && (m_histBarClassCounts != null || m_histBarCounts != null)) { double heightRatio; int x = 0, y = 0, barWidth; // If the class attribute is set and is not numeric then draw coloured // subbars for the histogram bars if ((m_classIndex >= 0) && (m_data.attribute(m_classIndex).isNominal())) { // There is a padding of 3px on each side of the histogram. barWidth = ((this.getWidth() - 6) / m_histBarClassCounts.length) < 1 ? 1 : ((this.getWidth() - 6) / m_histBarClassCounts.length); // initializing x to start at the start of the 1st bar after // padding. x = 3; // Adding appropriate value to x to account for a "centered" // histogram if ((this.getWidth() - (x + m_histBarClassCounts.length * barWidth)) > 5) { // We take the current value of x (histogram's RHS padding) and // add // the barWidths of all the bars to it to us the size of // our histogram. We subtract that from the width of the panel // giving us the extra space that would be left if the histogram // is // drawn and divide that by 2 to get the midpoint of that extra // space. That space is then added to our x, hence making the // histogram centered. x += (this.getWidth() - (x + m_histBarClassCounts.length * barWidth)) / 2; } for (SparseInstance m_histBarClassCount : m_histBarClassCounts) { if (m_histBarClassCount != null) { // Calculating height ratio. Leave space of 19 for an axis line // at // the bottom heightRatio = (this.getHeight() - (float) m_fm.getHeight() - buttonHeight - 19) / m_maxValue; y = this.getHeight() - 19; // This would hold the count of the bar (sum of sub-bars). double sum = 0; for (int j = 0; j < m_histBarClassCount.numValues(); j++) { y = (int) (y - Math.round(m_histBarClassCount.valueSparse(j) * heightRatio)); // System.out.println("Filling x:"+x+" y:"+y+" width:"+barWidth+ // " height:"+ // (m_histBarClassCounts[i][j]*heightRatio)); // selecting the color corresponding to our class g.setColor(m_colorList.get(m_histBarClassCount.index(j))); // drawing the bar if its width is greater than 1 if (barWidth > 1) { g.fillRect( x, y, barWidth, (int) Math.round(m_histBarClassCount.valueSparse(j) * heightRatio)); } else if ((m_histBarClassCount.valueSparse(j) * heightRatio) > 0) { g.drawLine( x, y, x, (int) (y + Math.round(m_histBarClassCount.valueSparse(j) * heightRatio))); } g.setColor(Color.black); sum = sum + m_histBarClassCount.valueSparse(j); } // Drawing bar count on the top of the bar if it is < barWidth if (m_fm.stringWidth(" " + Utils.doubleToString(sum, 1)) < barWidth) { g.drawString(" " + Utils.doubleToString(sum, 1), x, y - 1); } // Moving x to the next bar x = x + barWidth; } } // Now drawing the axis line at the bottom of the histogram // initializing x again to the start of the plot x = 3; if ((this.getWidth() - (x + m_histBarClassCounts.length * barWidth)) > 5) { x += (this.getWidth() - (x + m_histBarClassCounts.length * barWidth)) / 2; } g.drawLine(x, this.getHeight() - 17, (barWidth == 1) ? x + barWidth * m_histBarClassCounts.length - 1 : x + barWidth * m_histBarClassCounts.length, this.getHeight() - 17); // axis // line -- // see // footnote // 2. g.drawLine(x, this.getHeight() - 16, x, this.getHeight() - 12); // minimum // line g.drawString(Utils.doubleToString(m_as.numericStats.min, 2), x, this.getHeight() - 12 + m_fm.getHeight()); // minimum value g.drawLine(x + (barWidth * m_histBarClassCounts.length) / 2, this.getHeight() - 16, x + (barWidth * m_histBarClassCounts.length) / 2, this.getHeight() - 12); // median line // Drawing median value. X position for drawing the value is: from // start of the plot take the mid point and subtract from it half // of the width of the value to draw. g.drawString( Utils.doubleToString(m_as.numericStats.max / 2 + m_as.numericStats.min / 2, 2), x + (barWidth * m_histBarClassCounts.length) / 2 - m_fm.stringWidth(Utils.doubleToString(m_as.numericStats.max / 2 + m_as.numericStats.min / 2, 2)) / 2, this.getHeight() - 12 + m_fm.getHeight()); // median value g.drawLine((barWidth == 1) ? x + barWidth * m_histBarClassCounts.length - 1 : x + barWidth * m_histBarClassCounts.length, this.getHeight() - 16, (barWidth == 1) ? x + barWidth * m_histBarClassCounts.length - 1 : x + barWidth * m_histBarClassCounts.length, this.getHeight() - 12); // maximum line g.drawString( Utils.doubleToString(m_as.numericStats.max, 2), (barWidth == 1) ? x + barWidth * m_histBarClassCounts.length - m_fm.stringWidth(Utils.doubleToString(m_as.numericStats.max, 2)) - 1 : x + barWidth * m_histBarClassCounts.length - m_fm.stringWidth(Utils.doubleToString(m_as.numericStats.max, 2)), this.getHeight() - 12 + m_fm.getHeight()); // maximum // value -- // see 2. } else { // if class attribute is numeric // There is a padding of 3px on each side of the histogram. barWidth = ((this.getWidth() - 6) / m_histBarCounts.length) < 1 ? 1 : ((this.getWidth() - 6) / m_histBarCounts.length); // Same as above. Pls inside of the if stmt. x = 3; if ((this.getWidth() - (x + m_histBarCounts.length * barWidth)) > 5) { x += (this.getWidth() - (x + m_histBarCounts.length * barWidth)) / 2; } // Same as above for (double m_histBarCount : m_histBarCounts) { // calculating the ration of the component's height compared to // the maxValue in our current attribute. Leaving 19 pixels to // draw the axis at the bottom of the histogram. heightRatio = (this.getHeight() - (float) m_fm.getHeight() - buttonHeight - 19) / m_maxValue; y = (int) (this.getHeight() - Math.round(m_histBarCount * heightRatio) - 19); // System.out.println("Filling x:"+x+" y:"+y+" width:"+barWidth+ // " height:"+(m_histBarCounts[i]*heightRatio)); // same as in the if stmt above if (barWidth > 1) { g.drawRect(x, y, barWidth, (int) Math.round(m_histBarCount * heightRatio)); } else if ((m_histBarCount * heightRatio) > 0) { g.drawLine(x, y, x, (int) (y + Math.round(m_histBarCount * heightRatio))); } if (m_fm.stringWidth(" " + Utils.doubleToString(m_histBarCount, 1)) < barWidth) { g.drawString(" " + Utils.doubleToString(m_histBarCount, 1), x, y - 1); } x = x + barWidth; } // Now drawing the axis at the bottom of the histogram x = 3; if ((this.getWidth() - (x + m_histBarCounts.length * barWidth)) > 5) { x += (this.getWidth() - (x + m_histBarCounts.length * barWidth)) / 2; } // This is exact the same as in the if stmt above. See the inside of // the stmt for details g.drawLine(x, this.getHeight() - 17, (barWidth == 1) ? x + barWidth * m_histBarCounts.length - 1 : x + barWidth * m_histBarCounts.length, this.getHeight() - 17); // axis line g.drawLine(x, this.getHeight() - 16, x, this.getHeight() - 12); // minimum // line g.drawString(Utils.doubleToString(m_as.numericStats.min, 2), x, this.getHeight() - 12 + m_fm.getHeight()); // minimum value g.drawLine(x + (barWidth * m_histBarCounts.length) / 2, this.getHeight() - 16, x + (barWidth * m_histBarCounts.length) / 2, this.getHeight() - 12); // median line g.drawString( Utils.doubleToString(m_as.numericStats.max / 2 + m_as.numericStats.min / 2, 2), x + (barWidth * m_histBarCounts.length) / 2 - m_fm.stringWidth(Utils.doubleToString(m_as.numericStats.max / 2 + m_as.numericStats.min / 2, 2)) / 2, this.getHeight() - 12 + m_fm.getHeight()); // median value g.drawLine((barWidth == 1) ? x + barWidth * m_histBarCounts.length - 1 : x + barWidth * m_histBarCounts.length, this.getHeight() - 16, (barWidth == 1) ? x + barWidth * m_histBarCounts.length - 1 : x + barWidth * m_histBarCounts.length, this.getHeight() - 12); // maximum // line g.drawString( Utils.doubleToString(m_as.numericStats.max, 2), (barWidth == 1) ? x + barWidth * m_histBarCounts.length - m_fm.stringWidth(Utils.doubleToString(m_as.numericStats.max, 2)) - 1 : x + barWidth * m_histBarCounts.length - m_fm.stringWidth(Utils.doubleToString(m_as.numericStats.max, 2)), this.getHeight() - 12 + m_fm.getHeight()); // maximum // value } // System.out.println("barWidth:"+barWidth+ // " histBarCount:"+m_histBarCounts.length); } else { g.clearRect(0, 0, this.getWidth(), this.getHeight()); g.drawString( "Attribute is neither numeric nor nominal.", this.getWidth() / 2 - m_fm.stringWidth("Attribute is neither numeric nor nominal.") / 2, this.getHeight() / 2 - m_fm.getHeight() / 2); } } // <--end if of calculation thread else if (m_displayCurrentAttribute) { // if still calculation thread is // running plot g.clearRect(0, 0, this.getWidth(), this.getHeight()); g.drawString("Calculating. Please Wait...", this.getWidth() / 2 - m_fm.stringWidth("Calculating. Please Wait...") / 2, this.getHeight() / 2 - m_fm.getHeight() / 2); } else if (!m_displayCurrentAttribute) { g.clearRect(0, 0, this.getWidth(), this.getHeight()); g.drawString("Too many values to display.", this.getWidth() / 2 - m_fm.stringWidth("Too many values to display.") / 2, this.getHeight() / 2 - m_fm.getHeight() / 2); } } // <--end if(m_as==null) this means } /** * Main method to test this class from command line * * @param args The arff file and the index of the attribute to use */ public static void main(String[] args) { if (args.length != 3) { final JFrame jf = new JFrame("AttribVisualization"); AttributeVisualizationPanel ap = new AttributeVisualizationPanel(); try { Instances ins = new Instances(new FileReader(args[0])); ap.setInstances(ins); System.out.println("Loaded: " + args[0] + "\nRelation: " + ap.m_data.relationName() + "\nAttributes: " + ap.m_data.numAttributes()); ap.setAttribute(Integer.parseInt(args[1])); } catch (Exception ex) { ex.printStackTrace(); System.exit(-1); } System.out.println("The attributes are: "); for (int i = 0; i < ap.m_data.numAttributes(); i++) { System.out.println(ap.m_data.attribute(i).name()); } jf.setSize(500, 300); jf.getContentPane().setLayout(new BorderLayout()); jf.getContentPane().add(ap, BorderLayout.CENTER); jf.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); jf.setVisible(true); } else { System.out.println("Usage: java AttributeVisualizationPanel" + " [arff file] [index of attribute]"); } } } /* * t =(int) Math.ceil((float)( * (m_data.instance(k).value(m_attribIndex)-m_as.numericStats.min) / barRange)); * 1. This equation gives a value between (i-1)+smallfraction and i if the * attribute m_attribIndex for the current instances lies in the ith interval. * We then increment the value of our i-1th field of our histogram/barplot * array. If, for example, barRange=3 then, apart from the 1st interval, each * interval i has values in the range (minValue+3*i-1, minValue+3*i]. The 1st * interval has range [minValue, minValue+i]. Hence it can be seen in the code * we specifically handle t=0 separately. */ /** * (barWidth==1)?x+barWidth*m_histBarClassCounts.length-1 : * x+barWidth*m_histBarClassCounts.length 2. In the case barWidth==1 we subtract * 1 otherwise the line become one pixel longer than the actual size of the * histogram */




    © 2015 - 2024 Weber Informatics LLC | Privacy Policy