source: src/main/java/weka/gui/AttributeVisualizationPanel.java @ 8

Last change on this file since 8 was 4, checked in by gnappo, 14 years ago

Import di weka.

File size: 62.9 KB
RevLine 
[4]1/*
2 *    This program is free software; you can redistribute it and/or modify
3 *    it under the terms of the GNU General Public License as published by
4 *    the Free Software Foundation; either version 2 of the License, or
5 *    (at your option) any later version.
6 *
7 *    This program is distributed in the hope that it will be useful,
8 *    but WITHOUT ANY WARRANTY; without even the implied warranty of
9 *    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
10 *    GNU General Public License for more details.
11 *
12 *    You should have received a copy of the GNU General Public License
13 *    along with this program; if not, write to the Free Software
14 *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
15 */
16
17/*
18 *    AttributeVisualizationPanel.java
19 *    Copyright (C) 2003 University of Waikato, Hamilton, New Zealand
20 *
21 */
22
23package weka.gui;
24
25import weka.core.Attribute;
26import weka.core.AttributeStats;
27import weka.core.FastVector;
28import weka.core.Instances;
29import weka.core.SparseInstance;
30import weka.core.Utils;
31import weka.gui.visualize.PrintableComponent;
32import weka.gui.visualize.PrintablePanel;
33
34import java.awt.BorderLayout;
35import java.awt.Color;
36import java.awt.FlowLayout;
37import java.awt.Font;
38import java.awt.FontMetrics;
39import java.awt.Graphics;
40import java.awt.event.ComponentAdapter;
41import java.awt.event.ComponentEvent;
42import java.awt.event.ItemEvent;
43import java.awt.event.ItemListener;
44import java.awt.event.MouseEvent;
45import java.io.FileReader;
46
47import javax.swing.JComboBox;
48import javax.swing.JFrame;
49
50/**
51 * Creates a panel that shows a visualization of an
52 * attribute in a dataset. For nominal attribute it
53 * shows a bar plot, with each bar corresponding to
54 * each nominal value of the attribute with its height
55 * equal to the frequecy that value appears in the
56 * dataset. For numeric attributes, it displays a
57 * histogram. The width of an interval in the
58 * histogram is calculated using Scott's(1979)
59 * method: <br>
60 *    intervalWidth = Max(1, 3.49*Std.Dev*numInstances^(1/3))
61 * Then the number of intervals is calculated by: <br>
62 *   intervals = max(1, Math.round(Range/intervalWidth);
63 *
64 * @author Ashraf M. Kibriya (amk14@cs.waikato.ac.nz)
65 * @version $Revision: 5721 $
66 */
67public class AttributeVisualizationPanel
68  extends PrintablePanel {
69
70  /** for serialization */
71  private static final long serialVersionUID = -8650490488825371193L;
72 
73  /** This holds the current set of instances */
74  protected Instances m_data;
75 
76  /**
77   * This holds the attribute stats of the current attribute on display. It is
78   * calculated in setAttribute(int idx) when it is called to set a new
79   * attribute index.
80   */
81  protected AttributeStats m_as;
82 
83  /** This holds the index of the current attribute on display and should be
84   *  set through setAttribute(int idx).
85   */
86  protected int m_attribIndex;
87 
88  /**
89   * This holds the max value of the current attribute. In case of nominal
90   * attribute it is the highest count that a nominal value has in the
91   * attribute (given by m_as.nominalWeights[i]), otherwise in case of numeric
92   * attribute it is simply the maximum value present in the attribute (given by
93   * m_as.numericStats.max). It is used to calculate the ratio of the height of
94   * the bars with respect to the height of the display area.
95   */
96  protected double m_maxValue;
97 
98  /**
99   * This array holds the count (or height) for the each of the bars in a
100   * barplot or a histogram. In case of barplots (and current attribute being
101   * nominal) its length (and the number of bars) is equal to the number of
102   * nominal values in the current attribute, with each field of the array being
103   * equal to the count of each nominal that it represents (the count of ith
104   * nominal value of an attribute is given by m_as.nominalWeights[i]). Whereas,
105   * in case of histograms (and current attribute being numeric) the width of
106   * its intervals is calculated by Scott's(1979) method: <br>
107   *    intervalWidth = Max(1, 3.49*Std.Dev*numInstances^(1/3))
108   * And the number of intervals by: <br>
109   *   intervals = max(1, Math.round(Range/intervalWidth);
110   * Then each field of this array contains the number of values of the current
111   * attribute that fall in the histogram interval that it represents. <br>
112   * NOTE: The values of this array are only calculated if the class attribute
113   * is not set or if it is numeric.
114   */
115  protected double[] m_histBarCounts;
116 
117  /**
118   * This array holds the per class count (or per class height) of the each of
119   * the bars in a barplot or a histogram.
120   * For nominal attributes the format is: <br>
121   *    m_histBarClassCounts[nominalValue][classValue+1].
122   * For numeric attributes the format is: <br>
123   *    m_histBarClassCounts[interval][classValues+1], <br>
124   *      where the number of intervals is calculated by the Scott's method as
125   *            mentioned above.
126   * The array is initialized to have 1+numClasses to accomodate for instances
127   * with missing class value. The ones with missing class value are displayed
128   * as a black sub par in a histogram or a barplot.
129   *
130   * NOTE: The values of this array are only calculated if the class attribute
131   * is set and it is nominal.
132   */
133  SparseInstance m_histBarClassCounts[];
134 
135  /**
136   * Contains the range of each bar in a histogram. It is used to work out the
137   * range of bar the mouse pointer is on in getToolTipText().
138   */
139  protected double m_barRange;
140 
141  /** Contains the current class index. */
142  protected int m_classIndex;
143 
144  /** This stores the BarCalc or HistCalc thread while a new barplot or
145   * histogram is being calculated. */
146  private Thread m_hc;
147 
148  /** True if the thread m_hc above is running. */
149  private boolean m_threadRun=false;
150 
151  private boolean m_doneCurrentAttribute = false;
152  private boolean m_displayCurrentAttribute = false;
153 
154  /** This stores and lets the user select a class attribute. It also has
155   * an entry "No Class" if the user does not want to set a class attribute
156   * for colouring.
157   */
158  protected JComboBox m_colorAttrib;
159 
160  /**
161   * Fontmetrics used to get the font size which is required for calculating
162   * displayable area size, bar height ratio and width of strings that are
163   * displayed on top of bars indicating their count.
164   */
165  private FontMetrics m_fm;
166 
167  /**
168   * Lock variable to synchronize the different threads running currently in
169   * this class. There are two to three threads in this class, AWT paint thread
170   * which is handled differently in paintComponent() which checks on
171   * m_threadRun to determine if it can perform full paint or not, the second
172   * thread is the main execution thread and the third is the one represented by
173   * m_hc which we start when we want to calculate the internal fields for a bar
174   * plot or a histogram.
175   */
176  private Integer m_locker = new Integer(1);
177 
178  //Image img;
179 
180  /** Contains discrete colours for colouring of subbars of histograms and
181   * bar plots when the class attribute is set and is nominal
182   */
183  private FastVector m_colorList = new FastVector();
184 
185  /** default colour list */
186  private static final Color [] m_defaultColors = {Color.blue,
187  Color.red,
188  Color.cyan,
189  new Color(75, 123, 130),
190  Color.pink,
191  Color.green,
192  Color.orange,
193  new Color(255, 0, 255),
194  new Color(255, 0, 0),
195  new Color(0, 255, 0),
196  };
197 
198  /**
199   * Constructor - If used then the class will not show the class selection
200   * combo box.
201   */
202  public AttributeVisualizationPanel() {
203    this(false);
204  }
205 
206  /**
207   * Constructor.
208   * @param showColouringOption - should be true if the class selection combo
209   * box is to be displayed with the histogram/barplot, or false otherwise.
210   * P.S: the combo box is always created it just won't be shown if
211   * showColouringOption is false.
212   */
213  public AttributeVisualizationPanel(boolean showColouringOption) {
214    this.setFont( new Font("Default", Font.PLAIN, 9) );
215    m_fm = this.getFontMetrics( this.getFont() );
216    this.setToolTipText("");
217    FlowLayout fl= new FlowLayout(FlowLayout.LEFT);
218    this.setLayout(fl);
219    this.addComponentListener( new ComponentAdapter() {
220      public void componentResized(ComponentEvent ce) {
221        if(m_data!=null) {
222//          calcGraph();
223        }
224      }
225    });
226   
227    m_colorAttrib = new JComboBox();
228    m_colorAttrib.addItemListener( new ItemListener() {
229      public void itemStateChanged(ItemEvent ie) {
230        if(ie.getStateChange()==ItemEvent.SELECTED) {
231          m_classIndex = m_colorAttrib.getSelectedIndex() - 1;
232          if (m_as != null) {
233            setAttribute(m_attribIndex);
234          }
235        }
236      }
237    });
238   
239    if(showColouringOption) {
240      //m_colorAttrib.setVisible(false);
241      this.add(m_colorAttrib);
242      validate();
243    }
244  }
245 
246  /**
247   * Sets the instances for use
248   *
249   * @param newins a set of Instances
250   */
251  public void setInstances(Instances newins) {
252    m_attribIndex = 0;
253    m_as = null;
254    m_data = new Instances(newins);
255    if(m_colorAttrib!=null) {
256      m_colorAttrib.removeAllItems();
257      m_colorAttrib.addItem("No class");
258      for(int i=0; i<m_data.numAttributes(); i++) {
259        String type = "";
260        switch (m_data.attribute(i).type()) {
261          case Attribute.NOMINAL:
262            type = "(Nom) ";
263            break;
264          case Attribute.NUMERIC:
265            type = "(Num) ";
266            break;
267          case Attribute.STRING:
268            type = "(Str) ";
269            break;
270          case Attribute.DATE:
271            type = "(Dat) ";
272            break;
273          case Attribute.RELATIONAL:
274            type = "(Rel) ";
275            break;
276          default:
277            type = "(???) ";
278        }
279        m_colorAttrib.addItem(new String("Class: "+m_data.attribute(i).name()+
280        " " + type));
281      }
282      if (m_data.classIndex() >= 0) {
283        m_colorAttrib.setSelectedIndex(m_data.classIndex() + 1);
284      } else {
285        m_colorAttrib.setSelectedIndex(m_data.numAttributes());
286      }
287      //if (m_data.classIndex() >= 0) {
288      //    m_colorAttrib.setSelectedIndex(m_data.classIndex());
289      //}
290    }
291    if (m_data.classIndex() >= 0) {
292      m_classIndex = m_data.classIndex();
293    } else {
294      m_classIndex = m_data.numAttributes()-1;
295    }
296   
297  }
298 
299  /**
300   * Returns the class selection combo box if the parent component wants to
301   * place it in itself or in some component other than this component.
302   */
303  public JComboBox getColorBox() {
304    return m_colorAttrib;
305  }
306 
307  /**
308   * Get the coloring (class) index for the plot
309   *
310   * @return an <code>int</code> value
311   */
312  public int getColoringIndex() {
313    return m_classIndex; //m_colorAttrib.getSelectedIndex();
314  }
315 
316  /**
317   * Set the coloring (class) index for the plot
318   *
319   * @param ci an <code>int</code> value
320   */
321  public void setColoringIndex(int ci) {
322    m_classIndex = ci;
323    if(m_colorAttrib!=null)
324      m_colorAttrib.setSelectedIndex(ci + 1);
325    else
326      setAttribute(m_attribIndex);
327  }
328 
329  /**
330   * Tells the panel which attribute to visualize.
331   *
332   * @param index The index of the attribute
333   */
334  public void setAttribute(int index) {
335   
336    synchronized (m_locker) {
337      //m_threadRun = true;
338      m_threadRun = false;
339      m_doneCurrentAttribute = false;
340      m_displayCurrentAttribute = true;
341      //if(m_hc!=null && m_hc.isAlive()) m_hc.stop();
342      m_attribIndex = index;
343      m_as = m_data.attributeStats(m_attribIndex);
344      //m_classIndex = m_colorAttrib.getSelectedIndex();
345    }
346    this.repaint();
347    // calcGraph();
348  }
349 
350  /**
351   * Recalculates the barplot or histogram to display, required usually when the
352   * attribute is changed or the component is resized.
353   */
354  public void calcGraph(int panelWidth, int panelHeight) {
355   
356    synchronized (m_locker) {
357      m_threadRun = true;
358      if(m_as.nominalWeights!=null) {
359        m_hc = new BarCalc(panelWidth, panelHeight);
360        m_hc.setPriority(m_hc.MIN_PRIORITY);
361        m_hc.start();
362      }
363      else if(m_as.numericStats!=null) {
364        m_hc = new HistCalc();
365        m_hc.setPriority(m_hc.MIN_PRIORITY);
366        m_hc.start();
367      } else {
368        m_histBarCounts = null;
369        m_histBarClassCounts = null;
370        m_doneCurrentAttribute = true;
371        m_threadRun = false;
372        this.repaint();
373      }
374    }
375  }
376 
377  /**
378   * Internal class that calculates the barplot to display, in a separate
379   * thread. In particular it initializes some of the crucial internal fields
380   * required by paintComponent() to display the histogram for the current
381   * attribute. These include: m_histBarCounts or m_histBarClassCounts,
382   * m_maxValue and m_colorList.
383   */
384  private class BarCalc extends Thread {
385    private int m_panelWidth;
386    private int m_panelHeight;
387   
388    public BarCalc(int panelWidth, int panelHeight) {
389      m_panelWidth = panelWidth;
390      m_panelHeight = panelHeight;
391    }
392   
393    public void run() {
394      synchronized (m_locker) {
395        // there is no use doing/displaying anything if the resolution
396        // of the panel is less than the number of values for this attribute
397        if (m_data.attribute(m_attribIndex).numValues() > m_panelWidth) {
398          m_histBarClassCounts = null;
399          m_threadRun = false;
400          m_doneCurrentAttribute = true;
401          m_displayCurrentAttribute = false;
402          AttributeVisualizationPanel.this.repaint();
403          return;
404        }
405       
406        if((m_classIndex >= 0) &&
407        (m_data.attribute(m_classIndex).isNominal())) {
408          SparseInstance histClassCounts[];
409          histClassCounts = new SparseInstance[m_data.attribute(m_attribIndex).numValues()];
410                                  //[m_data.attribute(m_classIndex).numValues()+1];
411         
412          if (m_as.nominalWeights.length > 0) {
413            m_maxValue = m_as.nominalWeights[0];
414            for(int i=0; i<m_data.attribute(m_attribIndex).numValues(); i++) {
415              if(m_as.nominalWeights[i]>m_maxValue)
416                m_maxValue = m_as.nominalWeights[i];
417            }
418          }
419          else {
420            m_maxValue = 0;
421          }
422         
423          if(m_colorList.size()==0)
424            m_colorList.addElement(Color.black);
425          for(int i=m_colorList.size();
426          i < m_data.attribute(m_classIndex).numValues()+1; i++) {
427            Color pc = m_defaultColors[(i-1) % 10];
428            int ija =  (i-1) / 10;
429            ija *= 2;
430           
431            for (int j=0;j<ija;j++) {
432              pc = pc.darker();
433            }
434           
435            m_colorList.addElement(pc);
436          }
437         
438          // first sort data on attribute values
439          m_data.sort(m_attribIndex);
440          double[] tempClassCounts = null;
441          int tempAttValueIndex = -1;
442         
443          for(int k=0; k<m_data.numInstances(); k++) {
444            //System.out.println("attrib: "+
445            //                   m_data.instance(k).value(m_attribIndex)+
446            //                   " class: "+
447            //                   m_data.instance(k).value(m_classIndex));
448            if(!m_data.instance(k).isMissing(m_attribIndex)) {
449              // check to see if we need to allocate some space here
450              if (m_data.instance(k).value(m_attribIndex) != tempAttValueIndex) {
451                if (tempClassCounts != null) {
452                  // set up the sparse instance for the previous bar (if any)
453                  int numNonZero = 0;
454                  for (int z = 0; z < tempClassCounts.length; z++) {
455                    if (tempClassCounts[z] > 0) {
456                      numNonZero++;
457                    }
458                  }
459                  double[] nonZeroVals = new double[numNonZero];
460                  int[] nonZeroIndices = new int[numNonZero];
461                  int count = 0;
462                  for (int z = 0; z < tempClassCounts.length; z++) {
463                    if (tempClassCounts[z] > 0) {
464                      nonZeroVals[count] = tempClassCounts[z];
465                      nonZeroIndices[count++] = z;
466                    }
467                  }
468                  SparseInstance tempS = 
469                    new SparseInstance(1.0, nonZeroVals, nonZeroIndices, tempClassCounts.length);
470                  histClassCounts[tempAttValueIndex] = tempS;
471                }
472               
473                tempClassCounts = new double[m_data.attribute(m_classIndex).numValues() + 1];
474                tempAttValueIndex = (int)m_data.instance(k).value(m_attribIndex);
475               
476                /* histClassCounts[(int)m_data.instance(k).value(m_attribIndex)] =
477                  new double[m_data.attribute(m_classIndex).numValues()+1]; */ 
478              }
479              if(m_data.instance(k).isMissing(m_classIndex)) {
480                /* histClassCounts[(int)m_data.instance(k).value(m_attribIndex)]
481                               [0] += m_data.instance(k).weight(); */
482                tempClassCounts[0] += m_data.instance(k).weight();
483              } else {
484                tempClassCounts[(int)m_data.instance(k).value(m_classIndex)+1] 
485                                += m_data.instance(k).weight();
486               
487                /*histClassCounts[(int)m_data.instance(k).value(m_attribIndex)]
488                              [(int)m_data.instance(k).value(m_classIndex)+1] += m_data.instance(k).weight();*/
489              }
490            }
491          }
492         
493          // set up sparse instance for last bar?
494          if (tempClassCounts != null) {
495            // set up the sparse instance for the previous bar (if any)
496            int numNonZero = 0;
497            for (int z = 0; z < tempClassCounts.length; z++) {
498              if (tempClassCounts[z] > 0) {
499                numNonZero++;
500              }
501            }
502            double[] nonZeroVals = new double[numNonZero];
503            int[] nonZeroIndices = new int[numNonZero];
504            int count = 0;
505            for (int z = 0; z < tempClassCounts.length; z++) {
506              if (tempClassCounts[z] > 0) {
507                nonZeroVals[count] = tempClassCounts[z];
508                nonZeroIndices[count++] = z;
509              }
510            }
511            SparseInstance tempS = 
512              new SparseInstance(1.0, nonZeroVals, nonZeroIndices, tempClassCounts.length);
513            histClassCounts[tempAttValueIndex] = tempS;
514          }
515         
516          //for(int i=0; i<histClassCounts.length; i++) {
517          //int sum=0;
518          //for(int j=0; j<histClassCounts[i].length; j++) {
519          //    sum = sum+histClassCounts[i][j];
520          //}
521          //System.out.println("histCount: "+sum+" Actual: "+
522          //                   m_as.nominalWeights[i]);
523          //}
524         
525          m_threadRun=false;
526          m_doneCurrentAttribute = true;
527          m_displayCurrentAttribute = true;
528          m_histBarClassCounts = histClassCounts;
529          //Image tmpImg = new BufferedImage(getWidth(), getHeight(),
530          //                                 BufferedImage.TYPE_INT_RGB);
531          //drawGraph( tmpImg.getGraphics() );
532          //img = tmpImg;
533          AttributeVisualizationPanel.this.repaint();
534        }
535        else {
536          double histCounts[];
537          histCounts  = new double[m_data.attribute(m_attribIndex).numValues()];
538         
539          if (m_as.nominalWeights.length > 0) {
540            m_maxValue = m_as.nominalWeights[0];
541            for(int i=0; i<m_data.attribute(m_attribIndex).numValues(); i++) {
542              if(m_as.nominalWeights[i]>m_maxValue)
543                m_maxValue = m_as.nominalWeights[i];
544            }
545          }
546          else {
547            m_maxValue = 0;
548          }
549         
550          for(int k=0; k<m_data.numInstances(); k++) {
551            if(!m_data.instance(k).isMissing(m_attribIndex))
552              histCounts[(int)m_data.instance(k).value(m_attribIndex)] += 
553                m_data.instance(k).weight();
554          }
555          m_threadRun=false;
556          m_displayCurrentAttribute = true;
557          m_doneCurrentAttribute = true;
558          m_histBarCounts = histCounts;
559          //Image tmpImg = new BufferedImage(getWidth(), getHeight(),
560          //                                 BufferedImage.TYPE_INT_RGB);
561          //drawGraph( tmpImg.getGraphics() );
562          //img = tmpImg;
563          AttributeVisualizationPanel.this.repaint();
564        }
565      } //end synchronized
566    }  //end run()
567  }
568 
569  /**
570   * Internal class that calculates the histogram to display, in a separate
571   * thread. In particular it initializes some of the crucial internal fields
572   * required by paintComponent() to display the histogram for the current
573   * attribute. These include: m_histBarCounts or m_histBarClassCounts,
574   * m_maxValue and m_colorList.
575   */
576  private class HistCalc extends Thread {
577    public void run() {
578      synchronized (m_locker) {
579        if((m_classIndex >= 0) &&
580           (m_data.attribute(m_classIndex).isNominal())) {
581         
582          int intervals; double intervalWidth=0.0;
583         
584          //This uses the M.P.Wand's method to calculate the histogram's
585          //interval width. See "Data-Based Choice of Histogram Bin Width", in
586          //The American Statistician, Vol. 51, No. 1, Feb., 1997, pp. 59-64.
587          //intervalWidth = Math.pow(6D/( -psi(2, g21())*m_data.numInstances()),
588          //                          1/3D );
589         
590          //This uses the Scott's method to calculate the histogram's interval
591          //width. See "On optimal and data-based histograms".
592          // See Biometrika, 66, 605-610 OR see the same paper mentioned above.
593          intervalWidth =  3.49 * m_as.numericStats.stdDev *
594                           Math.pow(m_data.numInstances(), -1/3D);
595          //The Math.max is introduced to remove the possibility of
596          //intervals=0 and =NAN that can happen if respectively all the numeric
597          //values are the same or the interval width is evaluated to zero.
598          intervals = Math.max(1,
599          (int)Math.round( (m_as.numericStats.max - m_as.numericStats.min) /
600                           intervalWidth) );
601         
602          //System.out.println("Max: "+m_as.numericStats.max+
603          //                   " Min: "+m_as.numericStats.min+
604          //                   " stdDev: "+m_as.numericStats.stdDev+
605          //                   "intervalWidth: "+intervalWidth);
606         
607          //The number 4 below actually represents a padding of 3 pixels on
608          //each side of the histogram, and is also reflected in other parts of
609          //the code in the shape of numerical constants like "6" here.
610          if(intervals > AttributeVisualizationPanel.this.getWidth()) {
611            intervals = AttributeVisualizationPanel.this.getWidth()-6;
612            if(intervals<1)//if width is too small then use 1 and forget padding
613              intervals = 1;
614          }
615          double histClassCounts[][]  =
616                          new double[intervals]
617                                 [m_data.attribute(m_classIndex).numValues()+1];
618         
619          double barRange   = (m_as.numericStats.max - m_as.numericStats.min) /
620                              (double)histClassCounts.length;
621         
622          m_maxValue = 0;
623         
624          if(m_colorList.size()==0)
625            m_colorList.addElement(Color.black);
626          for(int i = m_colorList.size();
627          i < m_data.attribute(m_classIndex).numValues()+1; i++) {
628            Color pc = m_defaultColors[(i-1) % 10];
629            int ija =  (i-1) / 10;
630            ija *= 2;
631            for (int j=0;j<ija;j++) {
632              pc = pc.darker();
633            }
634            m_colorList.addElement(pc);
635          }
636         
637          for(int k=0; k<m_data.numInstances(); k++) {
638            int t=0; //This holds the interval that the attibute value of the
639                     //new instance belongs to.
640            try {
641              if(!m_data.instance(k).isMissing(m_attribIndex)) {
642                //1. see footnote at the end of this file
643                t = (int)Math.ceil( (float)(
644                (m_data.instance(k).value(m_attribIndex)-m_as.numericStats.min)
645                / barRange) );
646                if(t==0) {
647                  if(m_data.instance(k).isMissing(m_classIndex))
648                    histClassCounts[t][0] += m_data.instance(k).weight();
649                  else
650                    histClassCounts[t][(int)m_data.instance(k).value(m_classIndex)+1] +=
651                      m_data.instance(k).weight();
652                  //if(histCounts[t]>m_maxValue)
653                  //  m_maxValue = histCounts[t];
654                }
655                else {
656                  if(m_data.instance(k).isMissing(m_classIndex))
657                    histClassCounts[t-1][0] += m_data.instance(k).weight();
658                  else
659                    histClassCounts[t-1][(int)m_data.instance(k).value(m_classIndex)+1] +=
660                      m_data.instance(k).weight();
661                  //if(histCounts[t-1]>m_maxValue)
662                  //  m_maxValue = histCounts[t-1];
663                }
664              }
665            }
666            catch(ArrayIndexOutOfBoundsException ae) {
667              System.out.println("t:"+(t)+
668              " barRange:"+barRange+
669              " histLength:"+histClassCounts.length+
670              " value:"+m_data.instance(k).value(m_attribIndex)+
671              " min:"+m_as.numericStats.min+
672              " sumResult:"+
673              (m_data.instance(k).value(m_attribIndex) -
674              m_as.numericStats.min)+
675              " divideResult:"+
676              (float)((m_data.instance(k).value(m_attribIndex) -
677              m_as.numericStats.min) / barRange)+
678              " finalResult:"+
679              Math.ceil((float)((m_data.instance(k).value(m_attribIndex)-
680              m_as.numericStats.min) / barRange)) );
681            }
682          }
683          for(int i=0; i<histClassCounts.length; i++) {
684            double sum=0;
685            for(int j=0; j<histClassCounts[i].length; j++)
686              sum = sum+histClassCounts[i][j];
687            if(m_maxValue<sum)
688              m_maxValue = sum;
689          }
690         
691          // convert to sparse instances
692          SparseInstance[] histClassCountsSparse = 
693            new SparseInstance[histClassCounts.length];
694         
695          for (int i = 0; i < histClassCounts.length; i++) {
696            int numSparseValues = 0;
697            for (int j = 0; j < histClassCounts[i].length; j++) {
698              if (histClassCounts[i][j] > 0) {
699                numSparseValues++;
700              }
701            }
702            double[] sparseValues = new double[numSparseValues];
703            int[] sparseIndices = new int[numSparseValues];
704            int count = 0;
705            for (int j = 0; j < histClassCounts[i].length; j++) {
706              if (histClassCounts[i][j] > 0) {
707                sparseValues[count] = histClassCounts[i][j];
708                sparseIndices[count++] = j;
709              }
710            }
711           
712            SparseInstance tempS = 
713              new SparseInstance(1.0, sparseValues, sparseIndices, 
714                  histClassCounts[i].length);
715            histClassCountsSparse[i] = tempS;
716           
717          }
718         
719          m_histBarClassCounts = histClassCountsSparse;
720          m_barRange =  barRange;
721         
722        }
723        else { //else if the class attribute is numeric or the class is not set
724         
725          int intervals; double intervalWidth;
726          //At the time of this coding the
727          //possibility of datasets with zero instances
728          //was being dealt with in the
729          //PreProcessPanel of weka Explorer.
730         
731          //old method of calculating number of intervals
732          //intervals =  m_as.totalCount>10 ?
733          //                  (int)(m_as.totalCount*0.1):(int)m_as.totalCount;
734         
735          //This uses the M.P.Wand's method to calculate the histogram's
736          //interval width. See "Data-Based Choice of Histogram Bin Width", in
737          //The American Statistician, Vol. 51, No. 1, Feb., 1997, pp. 59-64.
738          //intervalWidth = Math.pow(6D/(-psi(2, g21())*m_data.numInstances() ),
739          //                          1/3D );
740         
741          //This uses the Scott's method to calculate the histogram's interval
742          //width. See "On optimal and data-based histograms".
743          // See Biometrika, 66, 605-610 OR see the same paper mentioned above.
744          intervalWidth =  3.49 * m_as.numericStats.stdDev *
745                           Math.pow(m_data.numInstances(), -1/3D);
746          //The Math.max is introduced to remove the possibility of
747          //intervals=0 and =NAN that can happen if respectively all the numeric
748          //values are the same or the interval width is evaluated to zero.
749          intervals = Math.max(1,
750          (int)Math.round( (m_as.numericStats.max - m_as.numericStats.min) /
751                           intervalWidth) );
752         
753          //The number 4 below actually represents a padding of 3 pixels on
754          //each side of the histogram, and is also reflected in other parts of
755          //the code in the shape of numerical constants like "6" here.
756          if(intervals > AttributeVisualizationPanel.this.getWidth()) {
757            intervals = AttributeVisualizationPanel.this.getWidth()-6;
758            if(intervals<1)
759              intervals = 1;
760          }
761         
762          double[] histCounts  = new double[intervals];
763          double barRange   = (m_as.numericStats.max - m_as.numericStats.min) /
764                              (double)histCounts.length;
765         
766          m_maxValue = 0;
767         
768          for(int k=0; k<m_data.numInstances(); k++) {
769            int t=0; //This holds the interval to which the current attribute's
770                    //value of this particular instance k belongs to.
771           
772            if(m_data.instance(k).isMissing(m_attribIndex)) 
773              continue; //ignore missing values
774           
775            try {
776              //1. see footnote at the end of this file
777              t =(int) Math.ceil((
778              (m_data.instance(k).value(m_attribIndex)-m_as.numericStats.min)
779              / barRange));
780              if(t==0) {
781                histCounts[t] += m_data.instance(k).weight();
782                if(histCounts[t]>m_maxValue)
783                  m_maxValue = histCounts[t];
784              }
785              else {
786                histCounts[t-1] += m_data.instance(k).weight();
787                if(histCounts[t-1]>m_maxValue)
788                  m_maxValue = histCounts[t-1];
789              }
790            }
791            catch(ArrayIndexOutOfBoundsException ae) {
792              ae.printStackTrace();
793              System.out.println("t:"+(t)+
794              " barRange:"+barRange+
795              " histLength:"+histCounts.length+
796              " value:"+m_data.instance(k).value(m_attribIndex)+
797              " min:"+m_as.numericStats.min+
798              " sumResult:"+
799              (m_data.instance(k).value(m_attribIndex)-m_as.numericStats.min)+
800              " divideResult:"+
801              (float)((m_data.instance(k).value(m_attribIndex) -
802              m_as.numericStats.min)/barRange)+
803              " finalResult:"+
804              Math.ceil( (float)((m_data.instance(k).value(m_attribIndex) -
805              m_as.numericStats.min) / barRange)) );
806            }
807          }
808          m_histBarCounts = histCounts;
809          m_barRange =  barRange;
810        }
811       
812        m_threadRun=false;
813        m_displayCurrentAttribute = true;
814        m_doneCurrentAttribute = true;
815        //Image tmpImg = new BufferedImage(getWidth(), getHeight(),
816        //                                 BufferedImage.TYPE_INT_RGB);
817        //drawGraph( tmpImg.getGraphics() );
818        //img = tmpImg;
819        AttributeVisualizationPanel.this.repaint();
820      }
821    }
822   
823    /****Code for M.P.Wand's method of histogram bin width selection.
824     *   There is some problem with it. It always comes up -ve value
825     *   which is raised to the power 1/3 and gives an NAN.
826     * private static final int M=400;
827     * private double psi(int r, double g) {
828     * double val;
829     *
830     * double sum=0.0;
831     * for(int i=0; i<M; i++) {
832     * double valCjKj=0.0;
833     * for(int j=0; j<M; j++) {
834     * valCjKj += c(j) * k(r, j-i, g);
835     * }
836     * sum += valCjKj*c(i);
837     * }
838     *
839     * val = Math.pow(m_data.numInstances(), -2) * sum;
840     * //System.out.println("psi returns: "+val);
841     * return val;
842     * }
843     * private double g21() {
844     * double val;
845     *
846     * val = Math.pow(2 / ( Math.sqrt(2D*Math.PI)*psi(4, g22()) *
847     *                      m_data.numInstances() ), 1/5D)
848     *       * Math.sqrt(2) * m_as.numericStats.stdDev;
849     * //System.out.println("g21 returns: "+val);
850     * return val;
851     * }
852     * private double g22() {
853     * double val;
854     *
855     * val = Math.pow( 2D/(5*m_data.numInstances()), 1/7D) *
856     *       Math.sqrt(2) * m_as.numericStats.stdDev;
857     * //System.out.println("g22 returns: "+val);
858     * return val;
859     * }
860     * private double c(int j) {
861     * double val=0.0;
862     * double sigma = (m_as.numericStats.max - m_as.numericStats.min)/(M-1);
863     *
864     * //System.out.println("In c before doing the sum we have");
865     * //System.out.println("max: " +m_as.numericStats.max+" min: "+
866     * //                   m_as.numericStats.min+" sigma: "+sigma);
867     *
868     * for(int i=0; i<m_data.numInstances(); i++) {
869     * if(!m_data.instance(i).isMissing(m_attribIndex))
870     * val += Math.max( 0,
871     * ( 1 - Math.abs( Math.pow(sigma, -1)*(m_data.instance(i).value(m_attribIndex) - j) ) )
872     * );
873     * }
874     * //System.out.println("c returns: "+val);
875     * return val;
876     * }
877     * private double k(int r, int j, double g) {
878     * double val;
879     * double sigma = (m_as.numericStats.max - m_as.numericStats.min)/(M-1);
880     * //System.out.println("Before calling L we have");
881     * //System.out.println("Max: "+m_as.numericStats.max+" Min: "+m_as.numericStats.min+"\n"+
882     * //                        "r: "+r+" j: "+j+" g: "+g);
883     * val = Math.pow( g, -r-1) * L(sigma*j/g);
884     * //System.out.println("k returns: "+val);
885     * return val;
886     * }
887     * private double L(double x) {
888     * double val;
889     *
890     * val = Math.pow( 2*Math.PI, -1/2D ) * Math.exp( -(x*x)/2D );
891     * //System.out.println("L returns: "+val);
892     * return val;
893     * }
894     *******End of Wand's method
895     */
896  }
897 
898 
899  /**
900   * Returns "&lt;nominal value&gt; [&lt;nominal value count&gt;]"
901   * if displaying a bar plot and mouse is on some bar.
902   * If displaying histogram then it
903   *     <li>returns "count &lt;br&gt; [&lt;bars Range&gt;]" if mouse is
904   *     on the first bar. </li>
905   *     <li>returns "count &lt;br&gt; (&lt;bar's Range&gt;]" if mouse is
906   *     on some bar other than the first one. </li>
907   * Otherwise it returns ""
908   *
909   * @param ev The mouse event
910   */
911  public String getToolTipText(MouseEvent ev) {
912   
913    if(m_as!=null && m_as.nominalWeights!=null) { //if current attrib is nominal
914     
915      float intervalWidth = this.getWidth() / (float)m_as.nominalWeights.length;
916      double heightRatio;     
917      int barWidth, x=0, y=0;
918     
919      //if intervalWidth is at least six then bar width is 80% of intervalwidth
920      if(intervalWidth>5) //the rest is padding
921        barWidth = (int)Math.floor(intervalWidth*0.8F);
922      else
923        barWidth = 1;  //Otherwise barwidth is 1 & padding would be at least 1.
924     
925      //initializing x to maximum of 1 or 10% of interval width (i.e. half of
926      //the padding which is 20% of interval width, as there is 10% on each
927      //side of the bar) so that it points to the start of the 1st bar
928      x = x + (int)( (Math.floor(intervalWidth*0.1F))<1 ? 
929                     1:(Math.floor(intervalWidth*0.1F)) );
930
931      //Adding to x the appropriate value so that it points to the 1st bar of
932      //our "centered" barplot. If subtracting barplots width from panel width
933      //gives <=2 then the barplot is not centered.
934      if(this.getWidth() - (m_as.nominalWeights.length*barWidth+
935                           (int)( (Math.floor(intervalWidth*0.2F))<1 ? 
936                                   1:(Math.floor(intervalWidth*0.2F)) ) * 
937                           m_as.nominalWeights.length) > 2 ) {
938       
939        //The following amounts to adding to x the half of the area left after
940        //subtracting from the components width the width of the whole barplot
941        //(i.e. width of all the bars plus the width of all the bar paddings,
942        //thereby equaling to the whole barplot), since our barplot is centered.
943        x += ( this.getWidth() - (m_as.nominalWeights.length*barWidth + 
944                                 (int)( (Math.floor(intervalWidth*0.2F))<1 ? 
945                                        1:(Math.floor(intervalWidth*0.2F)) ) * 
946                                 m_as.nominalWeights.length) ) / 2;
947      }
948       
949      for(int i=0; i<m_as.nominalWeights.length; i++) {       
950        heightRatio = (this.getHeight()-(double)m_fm.getHeight())/m_maxValue;
951        //initializing y to point to (from top) the start of the bar
952        y = (int) (this.getHeight()-Math.round(m_as.nominalWeights[i]*heightRatio));
953       
954        //if our mouse is on a bar then return the count of this bar in our
955        //barplot
956        if(ev.getX() >= x && ev.getX()<=x+barWidth && 
957           ev.getY() >= this.getHeight() - 
958                        Math.round(m_as.nominalWeights[i]*heightRatio) )
959          return(m_data.attribute(m_attribIndex).value(i)+
960                 " ["+Utils.doubleToString(m_as.nominalWeights[i], 3)+"]");
961        //otherwise advance x to next bar and check that. Add barwidth to x
962        //and padding which is max(1, 20% of interval width)
963        x = x+barWidth+(int)( (Math.floor(intervalWidth*0.2F))<1 ? 
964                              1:(Math.floor(intervalWidth*0.2F)) );
965      }
966    }
967    else if(m_threadRun==false &&     //if attrib is numeric
968            (m_histBarCounts!=null || m_histBarClassCounts!=null)) {
969
970      double heightRatio, intervalWidth;
971      int x=0, y=0,  barWidth;
972      double bar = m_as.numericStats.min;
973     
974      //if the class attribute is set and it is nominal
975      if((m_classIndex >= 0) && (m_data.attribute(m_classIndex).isNominal())) {
976        //there is 3 pixels of padding on each side of the histogram
977        //the barwidth is 1 if after removing the padding its width is less
978        //then the displayable width
979        barWidth = ((this.getWidth()-6)/m_histBarClassCounts.length)<1 ? 
980                   1:((this.getWidth()-6)/m_histBarClassCounts.length);
981       
982        //initializing x to 3 adding appropriate value to make it point to the
983        //start of the 1st bar of our "centered" histogram.
984        x = 3;
985        if( (this.getWidth() - (x + m_histBarClassCounts.length*barWidth)) > 5 )
986          x += (this.getWidth() - (x + m_histBarClassCounts.length*barWidth))/2;
987       
988        heightRatio = (this.getHeight()-(double)m_fm.getHeight())/m_maxValue;
989       
990        if( ev.getX()-x >= 0) {
991          //The temp holds the index of the current interval that we are looking
992          //at
993          int temp = (int)((ev.getX()-x)/(barWidth+0.0000000001));
994          if(temp == 0){  //handle the special case temp==0. see footnote 1
995            double sum=0;
996            for(int k=0; k<m_histBarClassCounts[0].numValues(); k++)
997              sum += m_histBarClassCounts[0].valueSparse(k);
998            //return the count of the interval mouse is pointing to plus
999            //the range of values that fall into this interval
1000            return ("<html><center><font face=Dialog size=-1>" + 
1001                Utils.doubleToString(sum, 3) + "<br>"+
1002                    "["+Utils.doubleToString(bar+m_barRange*temp,3)+
1003                    ", "+Utils.doubleToString((bar+m_barRange*(temp+1)),3)+
1004                    "]"+"</font></center></html>");
1005          }
1006          else if( temp < m_histBarClassCounts.length ) { //handle case temp!=0
1007            double sum=0;
1008            for(int k=0; k<m_histBarClassCounts[temp].numValues(); k++)
1009              sum+=m_histBarClassCounts[temp].valueSparse(k);
1010            //return the count of the interval mouse is pointing to plus
1011            //the range of values that fall into this interval
1012            return ("<html><center><font face=Dialog size=-1>" + 
1013                Utils.doubleToString(sum, 3) + "<br>("+
1014                    Utils.doubleToString(bar+m_barRange*temp,3)+", "+
1015                    Utils.doubleToString((bar+m_barRange*(temp+1)),3)+
1016                    "]</font></center></html>");
1017          }
1018        }
1019      }
1020      else {  //else if the class attribute is not set or is numeric
1021        barWidth = ((this.getWidth()-6)/m_histBarCounts.length) < 1 ? 
1022                   1 : ((this.getWidth()-6)/m_histBarCounts.length);
1023       
1024        //initializing x to 3 adding appropriate value to make it point to the
1025        //start of the 1st bar of our "centered" histogram.
1026        x = 3;
1027        if( (this.getWidth() - (x + m_histBarCounts.length*barWidth)) > 5 )
1028          x += (this.getWidth() - (x + m_histBarCounts.length*barWidth))/2;
1029       
1030        heightRatio = (this.getHeight()-(float)m_fm.getHeight())/m_maxValue;
1031       
1032        if( ev.getX()-x >= 0) {
1033          //Temp holds the index of the current bar we are looking at.
1034          int temp = (int)((ev.getX()-x)/(barWidth+0.0000000001));
1035         
1036          //return interval count as well as its range
1037          if(temp == 0) //handle special case temp==0. see footnote 1.
1038            return ("<html><center><font face=Dialog size=-1>"+
1039                    m_histBarCounts[0]+"<br>"+
1040                    "["+Utils.doubleToString(bar+m_barRange*temp,3)+", "+
1041                    Utils.doubleToString((bar+m_barRange*(temp+1)),3)+
1042                    "]"+
1043                    "</font></center></html>");
1044          else if(temp < m_histBarCounts.length) //handle case temp!=0
1045            return ("<html><center><font face=Dialog size=-1>"+
1046                    m_histBarCounts[temp]+"<br>"+
1047                    "("+Utils.doubleToString(bar+m_barRange*temp,3)+", "+
1048                    Utils.doubleToString((bar+m_barRange*(temp+1)),3)+
1049                    "]"+
1050                    "</font></center></html>");
1051        }
1052      }
1053    }
1054    return PrintableComponent.getToolTipText(m_Printer);
1055  }
1056 
1057 
1058  /**
1059   * Paints this component
1060   *
1061   * @param g The graphics object for this component
1062   */
1063  public void paintComponent(Graphics g) {
1064    g.clearRect(0,0,this.getWidth(), this.getHeight());
1065   
1066    if(m_as!=null) {    //If calculations have been done and histogram/barplot
1067      if (!m_doneCurrentAttribute && !m_threadRun) {
1068        calcGraph(this.getWidth(), this.getHeight());
1069      }
1070      if(m_threadRun==false && m_displayCurrentAttribute) {  //calculation thread is not running
1071        int buttonHeight=0;
1072       
1073        if(m_colorAttrib!=null)
1074          buttonHeight =m_colorAttrib.getHeight()+m_colorAttrib.getLocation().y;
1075       
1076        //if current attribute is nominal then draw barplot.
1077        if(m_as.nominalWeights != null && 
1078           (m_histBarClassCounts!=null || m_histBarCounts!=null) ) {
1079          double heightRatio, intervalWidth;
1080          int x=0, y=0, barHeight, barWidth;
1081         
1082          //if the class attribute is set and is nominal then draw coloured
1083          //subbars for each bar
1084          if((m_classIndex >= 0) && 
1085             (m_data.attribute(m_classIndex).isNominal())) {
1086               
1087            intervalWidth=(this.getWidth()/(float)m_histBarClassCounts.length);
1088           
1089            //Barwidth is 80% of interval width.The remaining 20% is padding,
1090            //10% on each side of the bar. If interval width is less then 5 the
1091            //20% of that value is less than 1, in that case we use bar width of
1092            //1 and padding of 1 pixel on each side of the bar.
1093            if(intervalWidth>5)
1094              barWidth = (int)Math.floor(intervalWidth*0.8F);
1095            else
1096              barWidth = 1;
1097
1098            //initializing x to 10% of interval width or to 1 if 10% is <1. This
1099            //is essentially the LHS padding of the 1st bar.
1100            x = x + (int)( (Math.floor(intervalWidth*0.1F))<1 ?
1101                           1 : (Math.floor(intervalWidth*0.1F)) );
1102           
1103            //Add appropriate value to x so that it starts at the 1st bar of
1104            //a "centered" barplot.
1105            if(this.getWidth() - (m_histBarClassCounts.length*barWidth + 
1106                                 (int)( (Math.floor(intervalWidth*0.2F))<1 ? 
1107                                        1 :(Math.floor(intervalWidth*0.2F))
1108                                      ) * m_histBarClassCounts.length) > 2 ) {
1109              //We take the width of all the bars and all the paddings (20%
1110              //of interval width), and subtract it from the width of the panel
1111              //to get the extra space that would be left after drawing. We
1112              //divide that space by 2 to get its mid-point and add that to our
1113              //x, thus making the whole bar plot drawn centered in our
1114              //component.
1115              x += (this.getWidth()-(m_histBarClassCounts.length*barWidth+
1116                                    (int)( (Math.floor(intervalWidth*0.2F))<1 ?
1117                                           1 : (Math.floor(intervalWidth*0.2F))
1118                                         ) * m_histBarClassCounts.length))/2;
1119            }
1120           
1121            //this holds the count of the bar and will be calculated by adding
1122            //up the counts of individual subbars. It is displayed at the top
1123            //of each bar.
1124            double sum=0;
1125            for(int i=0; i<m_histBarClassCounts.length; i++) {
1126
1127              //calculating the proportion of the components height compared to
1128              //the maxvalue in our attribute, also taking into account the
1129              //height of font to display bars count and the height of the class
1130              //ComboBox.
1131              heightRatio = ( this.getHeight()-(double)m_fm.getHeight() - 
1132                  buttonHeight ) / m_maxValue;             
1133              y=this.getHeight();
1134              if (m_histBarClassCounts[i] != null) {
1135                for(int j=0; j<m_histBarClassCounts[i].numAttributes(); j++) {
1136                  sum = sum + m_histBarClassCounts[i].value(j);
1137                  y = (int) (y-Math.round(m_histBarClassCounts[i].value(j) * heightRatio));
1138                  //selecting the colour corresponding to the current class.
1139                  g.setColor( (Color)m_colorList.elementAt(j) );
1140                  g.fillRect(x, y, barWidth, 
1141                      (int) Math.round(m_histBarClassCounts[i].value(j) * heightRatio));
1142                  g.setColor(Color.black);
1143                }
1144              }
1145              //drawing the bar count at the top of the bar if it is less than
1146              //interval width. draw it 1px up to avoid touching the bar.
1147              if(m_fm.stringWidth(Utils.doubleToString(sum, 1))<intervalWidth)
1148                g.drawString(Utils.doubleToString(sum, 1), x, y-1);
1149              //advancing x to the next bar by adding bar width and padding
1150              //of both the bars (i.e. RHS padding of the bar just drawn and LHS
1151              //padding of the new bar).
1152              x = x+barWidth+(int)( (Math.floor(intervalWidth*0.2F))<1 ? 
1153                  1:(Math.floor(intervalWidth*0.2F)) );
1154              //reseting sum for the next bar.
1155              sum=0;
1156
1157            }
1158          }
1159          //else if class attribute is numeric or not set then draw black bars.
1160          else {
1161            intervalWidth =  (this.getWidth()/(float)m_histBarCounts.length);
1162           
1163            //same as in the case of nominal class (see inside of if stmt
1164            //corresponding to the current else above).
1165            if(intervalWidth>5)
1166              barWidth = (int)Math.floor(intervalWidth*0.8F);
1167            else
1168              barWidth = 1;
1169           
1170            //same as in the case of nominal class (see inside of if stmt
1171            //corresponding to the current else above).
1172            x = x + (int)( (Math.floor(intervalWidth*0.1F))<1 ? 
1173                           1:(Math.floor(intervalWidth*0.1F)) );
1174           
1175            //same as in the case of nominal class
1176            if( this.getWidth() - (m_histBarCounts.length*barWidth+
1177                                  (int)( (Math.floor(intervalWidth*0.2F))<1 ? 
1178                                         1:(Math.floor(intervalWidth*0.2F)) ) * 
1179                                  m_histBarCounts.length) > 2 ) {
1180              x += (this.getWidth() -(m_histBarCounts.length*barWidth + 
1181                                     (int)((Math.floor(intervalWidth*0.2F))<1 ? 
1182                                           1:(Math.floor(intervalWidth*0.2F)))*
1183                                     m_histBarCounts.length))/2;
1184            }
1185           
1186            for(int i=0; i<m_histBarCounts.length; i++) {
1187              //calculating the proportion of the height of the component
1188              //compared to the maxValue in our attribute.
1189              heightRatio = (this.getHeight()-(float)m_fm.getHeight() - 
1190                             buttonHeight) / m_maxValue;
1191              y = (int) (this.getHeight()-Math.round(m_histBarCounts[i]*heightRatio));
1192              g.fillRect(x, y, barWidth, 
1193                         (int) Math.round(m_histBarCounts[i]*heightRatio));
1194              //draw the bar count if it's width is smaller than intervalWidth.
1195              //draw it 1px above to avoid touching the bar.
1196              if(m_fm.stringWidth(Utils.doubleToString(m_histBarCounts[i], 1)) < 
1197                                    intervalWidth)
1198                g.drawString(Utils.doubleToString(m_histBarCounts[i], 1), x, y-1);
1199              //Advance x to the next bar by adding bar-width and padding
1200              //of the bars (RHS padding of current bar & LHS padding of next
1201              //bar).
1202              x = x+barWidth+(int)( (Math.floor(intervalWidth*0.2F))<1 ? 
1203                                     1:(Math.floor(intervalWidth*0.2F)) );
1204            }
1205          }
1206         
1207        } //<--end if m_as.nominalCount!=null
1208        //if the current attribute is numeric then draw a histogram.
1209        else if(m_as.numericStats != null && 
1210                (m_histBarClassCounts!=null || m_histBarCounts!=null)) {
1211
1212          double heightRatio;
1213          float intervalWidth;
1214          int x=0, y=0,  barWidth;
1215         
1216          //If the class attribute is set and is not numeric then draw coloured
1217          //subbars for the histogram bars
1218          if((m_classIndex >=0) && 
1219             (m_data.attribute(m_classIndex).isNominal())) {
1220           
1221            //There is a padding of 3px on each side of the histogram.
1222            barWidth = ((this.getWidth()-6)/m_histBarClassCounts.length)<1 ? 
1223                       1 : ((this.getWidth()-6)/m_histBarClassCounts.length);
1224           
1225            //initializing x to start at the start of the 1st bar after padding.
1226            x = 3;
1227            //Adding appropriate value to x to account for a "centered"
1228            //histogram
1229            if( (this.getWidth() - 
1230                (x + m_histBarClassCounts.length*barWidth)) > 5 ) {
1231              //We take the current value of x (histogram's RHS padding) and add
1232              //the barWidths of all the bars to it to us the size of
1233              //our histogram. We subtract that from the width of the panel
1234              //giving us the extra space that would be left if the histogram is
1235              //drawn and divide that by 2 to get the midpoint of that extra
1236              //space. That space is then added to our x, hence making the
1237              //histogram centered.
1238              x += ( this.getWidth() - 
1239                    (x + m_histBarClassCounts.length*barWidth) ) / 2;
1240            }
1241           
1242            for(int i=0; i<m_histBarClassCounts.length; i++) {
1243              if (m_histBarClassCounts[i] != null) {
1244                //Calculating height ratio. Leave space of 19 for an axis line at
1245                //the bottom
1246                heightRatio = (this.getHeight()-(float)m_fm.getHeight() - 
1247                    buttonHeight-19) / m_maxValue;
1248                y = this.getHeight()-19;
1249                //This would hold the count of the bar (sum of sub-bars).
1250                double sum = 0;
1251                for(int j=0; j<m_histBarClassCounts[i].numValues(); j++) {
1252                  y = (int) (y-Math.round(m_histBarClassCounts[i].valueSparse(j) * heightRatio));
1253                  //System.out.println("Filling x:"+x+" y:"+y+" width:"+barWidth+
1254                  //                   " height:"+
1255                  //                   (m_histBarClassCounts[i][j]*heightRatio));
1256                  //selecting the color corresponding to our class
1257                  g.setColor( (Color)m_colorList.elementAt(m_histBarClassCounts[i].index(j)) );
1258                  //drawing the bar if its width is greater than 1
1259                  if(barWidth>1)
1260                    g.fillRect(x, y, 
1261                        barWidth, 
1262                        (int) Math.round(m_histBarClassCounts[i].valueSparse(j)*heightRatio));
1263                  //otherwise drawing a line
1264                  else if((m_histBarClassCounts[i].valueSparse(j) * heightRatio)>0)
1265                    g.drawLine(x, y, x, 
1266                        (int) (y+Math.round(m_histBarClassCounts[i].valueSparse(j)*heightRatio)));
1267                  g.setColor(Color.black);
1268                  sum = sum + m_histBarClassCounts[i].valueSparse(j);
1269                }
1270                //Drawing bar count on the top of the bar if it is < barWidth
1271                if(m_fm.stringWidth(" "+Utils.doubleToString(sum, 1))<barWidth)
1272                  g.drawString(" "+Utils.doubleToString(sum, 1), x, y-1);
1273                //Moving x to the next bar
1274                x = x+barWidth;
1275              }
1276            }
1277           
1278            //Now drawing the axis line at the bottom of the histogram
1279            //initializing x again to the start of the plot
1280            x = 3;
1281            if( (this.getWidth() - 
1282                (x + m_histBarClassCounts.length*barWidth)) > 5 )
1283              x += (this.getWidth() - 
1284                   (x + m_histBarClassCounts.length*barWidth))/2;
1285           
1286            g.drawLine(x, this.getHeight()-17,
1287                       (barWidth==1)?x+barWidth*m_histBarClassCounts.length-1 : 
1288                                     x+barWidth*m_histBarClassCounts.length,
1289                       this.getHeight()-17); //axis line -- see footnote 2.
1290            g.drawLine(x, this.getHeight()-16, 
1291                       x, this.getHeight()-12); //minimum line
1292            g.drawString(Utils.doubleToString(m_as.numericStats.min, 2),
1293                         x,
1294                         this.getHeight()-12+m_fm.getHeight()); //minimum value
1295            g.drawLine(x+(barWidth*m_histBarClassCounts.length)/2,
1296                       this.getHeight()-16,
1297                       x+(barWidth*m_histBarClassCounts.length)/2,
1298                       this.getHeight()-12); //median line
1299            //Drawing median value. X position for drawing the value is: from
1300            //start of the plot take the mid point and subtract from it half
1301            //of the width of the value to draw.
1302            g.drawString(Utils.doubleToString(m_as.numericStats.max/2+m_as.numericStats.min/2, 2),
1303                         x+(barWidth*m_histBarClassCounts.length)/2 - 
1304                           m_fm.stringWidth(Utils.doubleToString(m_as.numericStats.max/2+m_as.numericStats.min/2, 2))/2,
1305                         this.getHeight()-12+m_fm.getHeight()); //median value
1306            g.drawLine((barWidth==1) ? x+barWidth*m_histBarClassCounts.length-1:
1307                                       x+barWidth*m_histBarClassCounts.length,
1308                       this.getHeight()-16,
1309                       (barWidth==1) ? x+barWidth*m_histBarClassCounts.length-1:
1310                                       x+barWidth*m_histBarClassCounts.length,
1311                       this.getHeight()-12); //maximum line
1312            g.drawString(Utils.doubleToString(m_as.numericStats.max, 2),
1313                         (barWidth==1) ?
1314              x+barWidth*m_histBarClassCounts.length-m_fm.stringWidth(Utils.doubleToString(m_as.numericStats.max, 2))-1:
1315              x+barWidth*m_histBarClassCounts.length-m_fm.stringWidth(Utils.doubleToString(m_as.numericStats.max, 2)),
1316              this.getHeight()-12+m_fm.getHeight()); //maximum value -- see 2.
1317          }
1318          else {  //if class attribute is numeric
1319            //There is a padding of 3px on each side of the histogram.
1320            barWidth = ((this.getWidth()-6)/m_histBarCounts.length) < 1 ? 
1321                        1:((this.getWidth()-6)/m_histBarCounts.length);
1322
1323            //Same as above. Pls inside of the if stmt.
1324            x = 3;
1325            if( (this.getWidth() - (x + m_histBarCounts.length*barWidth)) > 5 )
1326              x += (this.getWidth() - (x + m_histBarCounts.length*barWidth))/2;
1327           
1328            //Same as above
1329            for(int i=0; i<m_histBarCounts.length; i++) {
1330              //calculating the ration of the component's height compared to
1331              //the maxValue in our current attribute. Leaving 19 pixels to
1332              //draw the axis at the bottom of the histogram.
1333              heightRatio = (this.getHeight()-(float)m_fm.getHeight() - 
1334                             buttonHeight-19) / m_maxValue;
1335              y = (int) (this.getHeight() - 
1336                  Math.round(m_histBarCounts[i]*heightRatio)-19);
1337              //System.out.println("Filling x:"+x+" y:"+y+" width:"+barWidth+
1338              //                   " height:"+(m_histBarCounts[i]*heightRatio));
1339              //same as in the if stmt above
1340              if(barWidth>1)
1341                g.drawRect(x, y, barWidth, 
1342                           (int) Math.round(m_histBarCounts[i]*heightRatio));
1343              else if((m_histBarCounts[i]*heightRatio)>0)
1344                g.drawLine(x, y, 
1345                           x, (int) (y+Math.round(m_histBarCounts[i]*heightRatio)));
1346              if(m_fm.stringWidth(" "+Utils.doubleToString(m_histBarCounts[i], 1)) < 
1347                    barWidth)
1348                g.drawString(" "+Utils.doubleToString(m_histBarCounts[i], 1), x, y-1);
1349             
1350              x = x+barWidth;
1351            }
1352           
1353            //Now drawing the axis at the bottom of the histogram
1354            x = 3;
1355            if( (this.getWidth() - (x + m_histBarCounts.length*barWidth)) > 5 )
1356              x += (this.getWidth() - (x + m_histBarCounts.length*barWidth))/2;
1357           
1358            //This is exact the same as in the if stmt above. See the inside of
1359            //the stmt for details
1360            g.drawLine(x, this.getHeight()-17,
1361                       (barWidth==1) ? x+barWidth*m_histBarCounts.length-1 : 
1362                                       x+barWidth*m_histBarCounts.length,
1363                       this.getHeight()-17); //axis line
1364            g.drawLine(x, this.getHeight()-16, 
1365                       x, this.getHeight()-12); //minimum line
1366            g.drawString(Utils.doubleToString(m_as.numericStats.min, 2),
1367                         x,
1368                         this.getHeight()-12+m_fm.getHeight()); //minimum value
1369            g.drawLine(x+(barWidth*m_histBarCounts.length)/2,
1370                       this.getHeight()-16,
1371                       x+(barWidth*m_histBarCounts.length)/2,
1372                       this.getHeight()-12); //median line
1373            g.drawString(Utils.doubleToString(m_as.numericStats.max/2+m_as.numericStats.min/2, 2),
1374                         x+(barWidth*m_histBarCounts.length)/2 - 
1375                           m_fm.stringWidth(Utils.doubleToString(m_as.numericStats.max/2+m_as.numericStats.min/2, 2))/2,
1376                         this.getHeight()-12+m_fm.getHeight()); //median value
1377            g.drawLine((barWidth==1) ? x+barWidth*m_histBarCounts.length-1 : 
1378                                        x+barWidth*m_histBarCounts.length,
1379                       this.getHeight()-16,
1380                       (barWidth==1) ? x+barWidth*m_histBarCounts.length-1 : 
1381                                       x+barWidth*m_histBarCounts.length,
1382                       this.getHeight()-12); //maximum line
1383            g.drawString(Utils.doubleToString(m_as.numericStats.max, 2),
1384                         (barWidth==1) ? 
1385              x+barWidth*m_histBarCounts.length-m_fm.stringWidth(Utils.doubleToString(m_as.numericStats.max, 2))-1 : 
1386              x+barWidth*m_histBarCounts.length-m_fm.stringWidth(Utils.doubleToString(m_as.numericStats.max, 2)),
1387              this.getHeight()-12+m_fm.getHeight()); //maximum value
1388          }
1389          //System.out.println("barWidth:"+barWidth+
1390          //                   " histBarCount:"+m_histBarCounts.length);
1391         
1392        } else {
1393          g.clearRect(0, 0, this.getWidth(), this.getHeight());
1394          g.drawString("Attribute is neither numeric nor nominal.",
1395          this.getWidth()/2 - m_fm.
1396          stringWidth("Attribute is neither numeric nor nominal.")/2,
1397          this.getHeight()/2-m_fm.getHeight()/2);
1398        }
1399      } //<--end if of calculation thread
1400      else if (m_displayCurrentAttribute) {   //if still calculation thread is running plot
1401        g.clearRect(0, 0, this.getWidth(), this.getHeight());
1402        g.drawString("Calculating. Please Wait...",
1403        this.getWidth()/2 - m_fm.stringWidth("Calculating. Please Wait...")/2,
1404        this.getHeight()/2-m_fm.getHeight()/2);
1405      } else if (!m_displayCurrentAttribute) {
1406        g.clearRect(0, 0, this.getWidth(), this.getHeight());
1407        g.drawString("Too many values to display.",
1408        this.getWidth()/2 - m_fm.stringWidth("Too many values to display.")/2,
1409        this.getHeight()/2-m_fm.getHeight()/2);
1410      }
1411    } //<--end if(m_as==null) this means
1412  }
1413 
1414 
1415  /**
1416   * Main method to test this class from command line
1417   *
1418   * @param args The arff file and the index of the attribute to use
1419   */
1420  public static void main(String [] args) {
1421    if(args.length!=3) {
1422      final JFrame jf = new JFrame("AttribVisualization");
1423      AttributeVisualizationPanel ap = new AttributeVisualizationPanel();
1424      try {
1425        Instances ins = new Instances( new FileReader(args[0]) );
1426        ap.setInstances(ins);
1427        System.out.println("Loaded: "+args[0]+
1428                           "\nRelation: "+ap.m_data.relationName()+
1429                           "\nAttributes: "+ap.m_data.numAttributes());
1430        ap.setAttribute( Integer.parseInt(args[1]) );
1431      }
1432      catch(Exception ex) { ex.printStackTrace(); System.exit(-1); }
1433      System.out.println("The attributes are: ");
1434      for(int i=0; i<ap.m_data.numAttributes(); i++)
1435        System.out.println(ap.m_data.attribute(i).name());
1436     
1437      jf.setSize(500, 300);
1438      jf.getContentPane().setLayout( new BorderLayout() );
1439      jf.getContentPane().add(ap, BorderLayout.CENTER );
1440      jf.setDefaultCloseOperation( jf.EXIT_ON_CLOSE );
1441      jf.setVisible(true);
1442    }
1443    else
1444      System.out.println("Usage: java AttributeVisualizationPanel"+
1445                         " [arff file] [index of attribute]");
1446  }
1447}
1448
1449
1450/*
1451 * t =(int) Math.ceil((float)(
1452 *              (m_data.instance(k).value(m_attribIndex)-m_as.numericStats.min)
1453 *                           / barRange));
1454 * 1.
1455 * This equation gives a value between (i-1)+smallfraction and i if the
1456 * attribute m_attribIndex for the current instances lies in the ith
1457 * interval. We then increment the value of our i-1th field of our
1458 * histogram/barplot array.
1459 * If, for example, barRange=3 then, apart from the 1st
1460 * interval, each interval i has values in the range
1461 * (minValue+3*i-1, minValue+3*i]. The 1st interval has range
1462 * [minValue, minValue+i]. Hence it can be seen in the code we specifically
1463 * handle t=0 separately.
1464 *
1465 */
1466
1467
1468/**
1469 * (barWidth==1)?x+barWidth*m_histBarClassCounts.length-1 :
1470 *                                    x+barWidth*m_histBarClassCounts.length
1471 * 2.
1472 * In the case barWidth==1 we subtract 1 otherwise the line become one pixel
1473 * longer than the actual size of the histogram
1474 */
Note: See TracBrowser for help on using the repository browser.