content: /*
   *    This program is free software; you can redistribute it and/or modify
   *    it under the terms of the GNU General Public License as published by
   *    the Free Software Foundation; either version 2 of the License, or
   *    (at your option) any later version.
   *
   *    This program is distributed in the hope that it will be useful,
   *    but WITHOUT ANY WARRANTY; without even the implied warranty of
   *    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
   *    GNU General Public License for more details.
   *
   *    You should have received a copy of the GNU General Public License
   *    along with this program; if not, write to the Free Software
   *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
   */
  
  /*
   *   BoundaryVisualizer.java
   *   Copyright (C) 2002 University of Waikato, Hamilton, New Zealand
   *
   */
  
  package weka.gui.boundaryvisualizer;
  
  import weka.classifiers.Classifier;
  import weka.classifiers.AbstractClassifier;
  import weka.core.Attribute;
  import weka.core.FastVector;
  import weka.core.Instances;
  import weka.core.DenseInstance;
  import weka.core.TechnicalInformation;
  import weka.core.TechnicalInformationHandler;
  import weka.core.Utils;
  import weka.core.TechnicalInformation.Field;
  import weka.core.TechnicalInformation.Type;
  import weka.gui.ExtensionFileFilter;
  import weka.gui.GenericObjectEditor;
  import weka.gui.PropertyPanel;
  import weka.gui.visualize.ClassPanel;
  
  import java.awt.BorderLayout;
  import java.awt.Color;
  import java.awt.Dimension;
  import java.awt.Font;
  import java.awt.FontMetrics;
  import java.awt.Graphics;
  import java.awt.GridBagConstraints;
  import java.awt.GridBagLayout;
  import java.awt.GridLayout;
  import java.awt.event.ActionEvent;
  import java.awt.event.ActionListener;
  import java.awt.event.ItemEvent;
  import java.awt.event.ItemListener;
  import java.awt.event.MouseAdapter;
  import java.awt.event.MouseEvent;
  import java.beans.PropertyChangeEvent;
  import java.beans.PropertyChangeListener;
  import java.io.File;
  import java.io.FileOutputStream;
  import java.io.ObjectOutputStream;
  import java.util.Vector;
  
  import javax.swing.BorderFactory;
  import javax.swing.BoxLayout;
  import javax.swing.ButtonGroup;
  import javax.swing.DefaultComboBoxModel;
  import javax.swing.JButton;
  import javax.swing.JCheckBox;
  import javax.swing.JComboBox;
  import javax.swing.JFileChooser;
  import javax.swing.JLabel;
  import javax.swing.JOptionPane;
  import javax.swing.JPanel;
  import javax.swing.JRadioButton;
  import javax.swing.JTextField;
  
  /**
   * BoundaryVisualizer. Allows the visualization of classifier decision
   * boundaries in two dimensions. A supplied classifier is first
   * trained on supplied training data, then a data generator (currently
   * using kernels) is used to generate new instances at points fixed in
   * the two visualization dimensions but random in the other
   * dimensions. These instances are classified by the classifier and
   * plotted as points with colour corresponding to the probability
   * distribution predicted by the classifier. At present, 2 * 2^(#
   * non-fixed dimensions) points are generated from each kernel per
   * pixel in the display. In practice, fewer points than this are
   * actually classified because kernels are weighted (on a per-pixel
   * basis) according to the fixexd dimensions and kernels corresponding
   * to the lowest 1% of the weight mass are discarded. Predicted
   * probability distributions are weighted (acording to the fixed
   * visualization dimensions) and averaged to produce an RGB value for
   * the pixel. For more information, see<p>
   * 
   * Eibe Frank and Mark Hall (2003). Visualizing Class Probability
   * Estimators. Working Paper 02/03, Department of Computer Science,
   * University of Waikato.
   *
   * @author <a href="mailto:mhall@cs.waikato.ac.nz">Mark Hall</a>
   * @version $Revision: 6933 $
   * @since 1.0
   * @see JPanel 
   */
  public class BoundaryVisualizer
    extends JPanel implements TechnicalInformationHandler {
  
    /** for serialization */
    private static final long serialVersionUID = 3933877580074013208L;
  
    /**
     * Inner class to handle rendering the axis
     *
     * @author <a href="mailto:mhall@cs.waikato.ac.nz">Mark Hall</a>
     * @version $Revision: 6933 $
     * @since 1.0
     * @see JPanel
     */
    private class AxisPanel
      extends JPanel {
  
      /** for serialization */
      private static final long serialVersionUID = -7421022416674492712L;
      
      private static final int MAX_PRECISION = 10;
      private boolean m_vertical = false;
      private final int PAD = 5;
      private FontMetrics m_fontMetrics;
      private int m_fontHeight;
      
      public AxisPanel(boolean vertical) {
        m_vertical = vertical;
        this.setBackground(Color.black);
        //      Graphics g = this.getGraphics();
        String fontFamily = this.getFont().getFamily();
        Font newFont = new Font(fontFamily, Font.PLAIN, 10);
        this.setFont(newFont);
      }
  
      public Dimension getPreferredSize() {
        if (m_fontMetrics == null) {
  	Graphics g = this.getGraphics();
  	m_fontMetrics = g.getFontMetrics();
  	m_fontHeight = m_fontMetrics.getHeight();
        }
        if (!m_vertical) {
  	return new Dimension(this.getSize().width, PAD+2+m_fontHeight);
        }
        return new Dimension(50, this.getSize().height);
      }
  
      public void paintComponent(Graphics g) {
        super.paintComponent(g);
        this.setBackground(Color.black);
        if (m_fontMetrics == null) {
  	m_fontMetrics = g.getFontMetrics();
  	m_fontHeight = m_fontMetrics.getHeight();
        }
  
        Dimension d = this.getSize();
        Dimension d2 = m_boundaryPanel.getSize();
        g.setColor(Color.gray);
        int hf = m_fontMetrics.getAscent();
        if (!m_vertical) {
  	g.drawLine(d.width, PAD, d.width-d2.width, PAD);
  	// try and draw some scale values
  	if (getInstances() != null) {
  	  int precisionXmax = 1;
  	  int precisionXmin = 1;
  	  int whole = (int)Math.abs(m_maxX);
  	  double decimal = Math.abs(m_maxX) - whole;
  	  int nondecimal;
  	  nondecimal = (whole > 0) 
  	    ? (int)(Math.log(whole) / Math.log(10))
  	    : 1;
  	  
  	  precisionXmax = (decimal > 0) 
  	    ? (int)Math.abs(((Math.log(Math.abs(m_maxX)) / 
  			      Math.log(10))))+2
  	    : 1;
  	  if (precisionXmax > MAX_PRECISION) {
  	    precisionXmax = 1;
  	  }
  	  String maxStringX = Utils.doubleToString(m_maxX,
  						   nondecimal+1+precisionXmax
  						   ,precisionXmax);
  	  
  	  whole = (int)Math.abs(m_minX);
  	  decimal = Math.abs(m_minX) - whole;
  	  nondecimal = (whole > 0) 
  	    ? (int)(Math.log(whole) / Math.log(10))
  	    : 1;
  	  precisionXmin = (decimal > 0) 
  	    ? (int)Math.abs(((Math.log(Math.abs(m_minX)) / 
  			      Math.log(10))))+2
  	    : 1;
  	  if (precisionXmin > MAX_PRECISION) {
  	    precisionXmin = 1;
  	  }
  	  
  	  String minStringX = Utils.doubleToString(m_minX,
  						   nondecimal+1+precisionXmin,
  						   precisionXmin);
  	  g.drawString(minStringX,  d.width-d2.width, PAD+hf+2);
  	  int maxWidth = m_fontMetrics.stringWidth(maxStringX);
  	  g.drawString(maxStringX, d.width-maxWidth, PAD+hf+2);
  	}
        } else {
  	g.drawLine(d.width-PAD, 0, d.width-PAD, d2.height);
  	// try and draw some scale values
  	if (getInstances() != null) {
  	  int precisionYmax = 1;
  	  int precisionYmin = 1;
  	  int whole = (int)Math.abs(m_maxY);
  	  double decimal = Math.abs(m_maxY) - whole;
  	  int nondecimal;
  	  nondecimal = (whole > 0) 
  	    ? (int)(Math.log(whole) / Math.log(10))
  	    : 1;
  	  
  	  precisionYmax = (decimal > 0) 
  	    ? (int)Math.abs(((Math.log(Math.abs(m_maxY)) / 
  			      Math.log(10))))+2
  	    : 1;
  	  if (precisionYmax > MAX_PRECISION) {
  	    precisionYmax = 1;
  	  }
  	  String maxStringY = Utils.doubleToString(m_maxY,
  						   nondecimal+1+precisionYmax
  						   ,precisionYmax);
  	  
  	  whole = (int)Math.abs(m_minY);
  	  decimal = Math.abs(m_minY) - whole;
  	  nondecimal = (whole > 0) 
  	    ? (int)(Math.log(whole) / Math.log(10))
  	    : 1;
  	  precisionYmin = (decimal > 0) 
  	    ? (int)Math.abs(((Math.log(Math.abs(m_minY)) / 
  			      Math.log(10))))+2
  	    : 1;
  	  if (precisionYmin > MAX_PRECISION) {
  	    precisionYmin = 1;
  	  }
  	  
  	  String minStringY = Utils.doubleToString(m_minY,
  						   nondecimal+1+precisionYmin,
  						   precisionYmin);
  	  int maxWidth = m_fontMetrics.stringWidth(minStringY);
  	  g.drawString(minStringY,  d.width-PAD-maxWidth-2, d2.height);
  	  maxWidth = m_fontMetrics.stringWidth(maxStringY);
  	  g.drawString(maxStringY, d.width-PAD-maxWidth-2, hf);
  	}
        }
      }
    }
    
    /** the number of visualizer windows we have open. */
    protected static int m_WindowCount = 0; 
    
    /** whether the exit if there are no more windows open */
    protected static boolean m_ExitIfNoWindowsOpen = true;
  
    /** the training instances */
    private Instances m_trainingInstances;
  
    /** the classifier to use */
    private Classifier m_classifier;
  
    // plot area dimensions
    protected int m_plotAreaWidth = 384;
    //protected int m_plotAreaHeight = 384;
    protected int m_plotAreaHeight = 384;
  
    /** the plotting panel */
    protected BoundaryPanel m_boundaryPanel;
  
    // combo boxes for selecting the class attribute, class values (for
    // colouring pixels), and visualization attributes
    protected JComboBox m_classAttBox = new JComboBox();
    protected JComboBox m_xAttBox = new JComboBox();
    protected JComboBox m_yAttBox = new JComboBox();
  
    protected Dimension COMBO_SIZE = 
      new Dimension((int)(m_plotAreaWidth * 0.75),
  		  m_classAttBox.getPreferredSize().height);
  
    protected JButton m_startBut = new JButton("Start");
  
    protected JCheckBox m_plotTrainingData = new JCheckBox("Plot training data");
  
    protected JPanel m_controlPanel;
  
    protected ClassPanel m_classPanel = new ClassPanel();
  
    // separate panels for rendering axis information
    private AxisPanel m_xAxisPanel;
    private AxisPanel m_yAxisPanel;
  
    // min and max values for visualization dimensions
    private double m_maxX;
    private double m_maxY;
    private double m_minX;
    private double m_minY;
  
    private int m_xIndex;
    private int m_yIndex;
  
    /* Kernel density estimator/generator */
    private KDDataGenerator m_dataGenerator;
  
    /* number of samples per pixel (fixed dimensions only) */
    private int m_numberOfSamplesFromEachRegion;
  
    /** base for sampling in the non-fixed dimensions */
    private int m_generatorSamplesBase;
  
    /** Set the kernel bandwidth to cover this many nearest neighbours */
    private int m_kernelBandwidth;
    
    private JTextField m_regionSamplesText = 
      new JTextField(""+0);
  
    private JTextField m_generatorSamplesText = 
      new JTextField(""+0);
  
    private JTextField m_kernelBandwidthText = 
      new JTextField(""+3+"  ");
  
    //jimmy
    protected GenericObjectEditor m_classifierEditor = new GenericObjectEditor(); //the widget to select the classifier
    protected PropertyPanel m_ClassifierPanel = new PropertyPanel(m_classifierEditor);
    /** The file chooser for selecting arff files */
    protected JFileChooser m_FileChooser 
      = new JFileChooser(new File(System.getProperty("user.dir")));
    protected ExtensionFileFilter m_arffFileFilter = 
      new ExtensionFileFilter(Instances.FILE_EXTENSION,
  			    "Arff data files");
    protected JLabel dataFileLabel = new JLabel(); //stores the name of the data file (currently stores relation name rather than filename)
    protected JPanel m_addRemovePointsPanel = new JPanel(); //a panel which contains the controls to add and remove points
    protected JComboBox m_classValueSelector = new JComboBox(); //a widget to select the class attribute.
    protected JRadioButton m_addPointsButton = new JRadioButton(); //when this is selected, clicking on the BoundaryPanel will add points.
    protected JRadioButton m_removePointsButton = new JRadioButton(); //when this is selected, clicking on the BoundaryPanel will remove points.
    protected ButtonGroup m_addRemovePointsButtonGroup = new ButtonGroup();
    protected JButton removeAllButton = new JButton ("Remove all"); //button to remove all points
    protected JButton chooseButton = new JButton("Open File"); //button to choose a data file
    
    /* Register the property editors we need */
    static {
      GenericObjectEditor.registerEditors();
    }
    
    /**
     * Returns a string describing this tool
     * @return a description of the tool suitable for
     * displaying in various Weka GUIs
     */
    public String globalInfo() {
      return "Class for visualizing class probability estimates.\n\n"
      + "For more information, see\n\n"
      + getTechnicalInformation().toString();
    }
    
    /**
     * Returns an instance of a TechnicalInformation object, containing 
     * detailed information about the technical background of this class,
     * e.g., paper reference or book this class is based on.
     * 
     * @return the technical information about this class
     */
    public TechnicalInformation getTechnicalInformation() {
      TechnicalInformation        result;
  
      result = new TechnicalInformation(Type.INPROCEEDINGS);
      result.setValue(Field.AUTHOR, "Eibe Frank and Mark Hall");
      result.setValue(Field.TITLE, "Visualizing class probability estimators");
      result.setValue(Field.BOOKTITLE, "European Conference on Principles and Practice of " +
      		"Knowledge Discovery in Databases");
      result.setValue(Field.YEAR, "2003");
      result.setValue(Field.PAGES, "168-169");
      result.setValue(Field.PUBLISHER, "Springer-Verlag");
      result.setValue(Field.ADDRESS, "Cavtat-Dubrovnik");
  
      return result;
    }
  
  
    /**
     * Creates a new <code>BoundaryVisualizer</code> instance.
     */
    public BoundaryVisualizer() {
      
      setLayout(new BorderLayout());
      m_classAttBox.setMinimumSize(COMBO_SIZE);
      m_classAttBox.setPreferredSize(COMBO_SIZE);
      m_classAttBox.setMaximumSize(COMBO_SIZE);
      m_classAttBox.addActionListener(new ActionListener() {
        public void actionPerformed(ActionEvent e) {
          if (m_classAttBox.getItemCount() != 0)
  	{
  		try { 
  			m_classPanel.setCindex(m_classAttBox.getSelectedIndex());
  			plotTrainingData();
  			System.err.println("Here in class att box listener");
  		} catch (Exception ex) {ex.printStackTrace();}
  		
  		//set up the add points selector combo box. -jimmy
  		setUpClassValueSelectorCB();
  	}
        }
        });
  	    
  
      m_xAttBox.setMinimumSize(COMBO_SIZE);
      m_xAttBox.setPreferredSize(COMBO_SIZE);
      m_xAttBox.setMaximumSize(COMBO_SIZE);
  
      m_yAttBox.setMinimumSize(COMBO_SIZE);
      m_yAttBox.setPreferredSize(COMBO_SIZE);
      m_yAttBox.setMaximumSize(COMBO_SIZE);
  
      m_classPanel.setMinimumSize(new 
        Dimension((int)COMBO_SIZE.getWidth()*2, 
  		(int)COMBO_SIZE.getHeight()*2));
      m_classPanel.setPreferredSize(new 
        Dimension((int)COMBO_SIZE.getWidth()*2, 
  		(int)COMBO_SIZE.getHeight()*2));
  
  
      m_controlPanel = new JPanel();
      m_controlPanel.setLayout(new BorderLayout());
      
      //jimmy
      JPanel dataChooseHolder = new JPanel(new BorderLayout());
      dataChooseHolder.setBorder(BorderFactory.createTitledBorder("Dataset"));
      dataChooseHolder.add(dataFileLabel, BorderLayout.WEST);
      
      m_FileChooser.setFileSelectionMode(JFileChooser.FILES_ONLY);
      m_FileChooser.addChoosableFileFilter(m_arffFileFilter);
      chooseButton.addActionListener(new ActionListener() {
        public void actionPerformed(ActionEvent e) {
          try {
  		setInstancesFromFileQ();
  		int classIndex = m_classAttBox.getSelectedIndex();
  		if (m_trainingInstances != null && m_classifier != null && (m_trainingInstances.attribute(classIndex).isNominal())) {
  			m_startBut.setEnabled(true);
  //			plotTrainingData();
  		}
  		
  		
  	} catch (Exception ex) {
  		ex.printStackTrace(System.out);
  		System.err.println("exception");
  	}
  	
        }
      });
      dataChooseHolder.add(chooseButton, BorderLayout.EAST);
      
      JPanel classifierHolder = new JPanel();
      classifierHolder.setBorder(BorderFactory.createTitledBorder("Classifier"));
      classifierHolder.setLayout(new BorderLayout());
      m_classifierEditor.setClassType(weka.classifiers.Classifier.class);
      
      m_classifierEditor.addPropertyChangeListener(new PropertyChangeListener() {
      	public void propertyChange(PropertyChangeEvent evt) {
  		m_classifier = (Classifier)m_classifierEditor.getValue();
  		try {
  			int classIndex = m_classAttBox.getSelectedIndex();
  			if (m_trainingInstances != null && m_classifier != null && (m_trainingInstances.attribute(classIndex).isNominal())) {
  				m_startBut.setEnabled(true);
  			}
  		} catch (Exception ex) {};
  	}
      });
      classifierHolder.add(m_ClassifierPanel, BorderLayout.CENTER);
      
          
  
      JPanel cHolder = new JPanel();
      cHolder.setBorder(BorderFactory.createTitledBorder("Class Attribute"));
      cHolder.add(m_classAttBox);
  
      JPanel vAttHolder = new JPanel();
      vAttHolder.setLayout(new GridLayout(2,1));
      vAttHolder.setBorder(BorderFactory.
  			 createTitledBorder("Visualization Attributes"));
      vAttHolder.add(m_xAttBox);
      vAttHolder.add(m_yAttBox);
  
      JPanel colOne = new JPanel();
      colOne.setLayout(new BorderLayout());
      colOne.add(dataChooseHolder, BorderLayout.NORTH); //jimmy
      colOne.add(cHolder, BorderLayout.CENTER);
      //colOne.add(vAttHolder, BorderLayout.SOUTH);
  
      JPanel tempPanel = new JPanel();
      tempPanel.setBorder(BorderFactory.
  			createTitledBorder("Sampling control"));
      tempPanel.setLayout(new GridLayout(3,1));
  
      JPanel colTwo = new JPanel();
      colTwo.setLayout(new BorderLayout());
      JPanel gsP = new JPanel(); gsP.setLayout(new BorderLayout());
      gsP.add(new JLabel(" Base for sampling (r)"), BorderLayout.CENTER);
      gsP.add(m_generatorSamplesText, BorderLayout.WEST);
      tempPanel.add(gsP);
  
      JPanel rsP = new JPanel(); rsP.setLayout(new BorderLayout());
      rsP.add(new JLabel(" Num. locations per pixel"), BorderLayout.CENTER);
      rsP.add(m_regionSamplesText, BorderLayout.WEST);
      tempPanel.add(rsP);
  
      JPanel ksP = new JPanel(); ksP.setLayout(new BorderLayout());
      ksP.add(new JLabel(" Kernel bandwidth (k)"), BorderLayout.CENTER);
      ksP.add(m_kernelBandwidthText, BorderLayout.WEST);
      tempPanel.add(ksP);
      
      colTwo.add(classifierHolder,BorderLayout.NORTH);//jimmy
      //colTwo.add(tempPanel, BorderLayout.CENTER);
      colTwo.add(vAttHolder, BorderLayout.CENTER);
  
      JPanel startPanel = new JPanel();
      startPanel.setBorder(BorderFactory.
  			 createTitledBorder("Plotting"));
      startPanel.setLayout(new BorderLayout());
      startPanel.add(m_startBut, BorderLayout.CENTER);
      startPanel.add(m_plotTrainingData, BorderLayout.WEST);
  
      //colTwo.add(startPanel, BorderLayout.SOUTH);
  
      m_controlPanel.add(colOne, BorderLayout.WEST);
      m_controlPanel.add(colTwo, BorderLayout.CENTER);
      JPanel classHolder = new JPanel();
      classHolder.setLayout(new BorderLayout()); //jimmy
      classHolder.setBorder(BorderFactory.createTitledBorder("Class color"));
      classHolder.add(m_classPanel, BorderLayout.CENTER);
      m_controlPanel.add(classHolder, BorderLayout.SOUTH);
      
      JPanel aboutAndControlP = new JPanel();
      aboutAndControlP.setLayout(new BorderLayout());
      aboutAndControlP.add(m_controlPanel, BorderLayout.SOUTH);
      
      weka.gui.PropertySheetPanel psp = new weka.gui.PropertySheetPanel();
      psp.setTarget(BoundaryVisualizer.this);
      JPanel aboutPanel = psp.getAboutPanel();
      
      aboutAndControlP.add(aboutPanel, BorderLayout.NORTH);
  
      add(aboutAndControlP, BorderLayout.NORTH);
      
      //classHolder.add(newWindowButton, BorderLayout.EAST);
     
      // set up the add-remove points widgets
      m_addRemovePointsPanel.setBorder(BorderFactory.createTitledBorder("Add / remove data points"));
      m_addRemovePointsPanel.setLayout(new GridBagLayout());
      GridBagConstraints constraints = new GridBagConstraints();
      constraints.weightx = 1.0;
      constraints.weighty = 1.0;
      constraints.gridx = 0;
      constraints.gridy = 0;
      constraints.fill = GridBagConstraints.BOTH;
      m_addRemovePointsPanel.add(m_addPointsButton);
      constraints.gridx = 1;
      m_addRemovePointsPanel.add(new JLabel("Add points"), constraints);
      constraints.gridx = 2;
      m_addRemovePointsPanel.add(m_classValueSelector);
      constraints.gridx = 0;
      constraints.gridy = 1;
      m_addRemovePointsPanel.add(m_removePointsButton, constraints);
      constraints.gridx = 1;
      m_addRemovePointsPanel.add(new JLabel("Remove points"),constraints);
      
      
      	removeAllButton.addActionListener(new ActionListener() {
  		public void actionPerformed(ActionEvent e) {
  			if (m_trainingInstances != null)
  			{
  				if (m_startBut.getText().equals("Stop")) //we are plotting
  					return;
  				m_boundaryPanel.removeAllInstances();
  				computeBounds();
  				m_xAxisPanel.repaint(0,0,0,m_xAxisPanel.getWidth(), m_xAxisPanel.getHeight());
  				m_yAxisPanel.repaint(0,0,0,m_yAxisPanel.getWidth(), m_yAxisPanel.getHeight());
  	
  				try {m_boundaryPanel.plotTrainingData(); } catch (Exception ex) {}
  			}
  		}	
  	});
      constraints.gridx = 2;
      m_addRemovePointsPanel.add(removeAllButton, constraints);
      
  //     m_addRemovePointsPanel.add(addPointsFrame, BorderLayout.NORTH);
  //     m_addRemovePointsPanel.add(removePointsFrame, BorderLayout.CENTER);
      //m_addRemovePointsPanel.add(removeAllButton, BorderLayout.SOUTH);
      
      
      m_addRemovePointsButtonGroup.add(m_addPointsButton);
      m_addRemovePointsButtonGroup.add(m_removePointsButton);
      m_addPointsButton.setSelected(true);
          
      //classHolder.add(m_addRemovePointsPanel, BorderLayout.SOUTH);
      
  
      m_boundaryPanel = new BoundaryPanel(m_plotAreaWidth, m_plotAreaHeight);
      m_numberOfSamplesFromEachRegion = m_boundaryPanel.getNumSamplesPerRegion();
      m_regionSamplesText.setText(""+m_numberOfSamplesFromEachRegion+"  ");
      m_generatorSamplesBase = (int)m_boundaryPanel.getGeneratorSamplesBase();
      m_generatorSamplesText.setText(""+m_generatorSamplesBase+"  ");
  
      m_dataGenerator = new KDDataGenerator();
      m_kernelBandwidth = m_dataGenerator.getKernelBandwidth();
      m_kernelBandwidthText.setText(""+m_kernelBandwidth+"  ");
      m_boundaryPanel.setDataGenerator(m_dataGenerator);
      
       
      JPanel gfxPanel = new JPanel();
      gfxPanel.setLayout(new BorderLayout());
      gfxPanel.setBorder(BorderFactory.createEtchedBorder());
      //add(gfxPanel, BorderLayout.CENTER);
          
     // gfxPanel.add(m_addRemovePointsPanel, BorderLayout.NORTH);
      gfxPanel.add(m_boundaryPanel, BorderLayout.CENTER);
      m_xAxisPanel = new AxisPanel(false);
      gfxPanel.add(m_xAxisPanel, BorderLayout.SOUTH);
      m_yAxisPanel = new AxisPanel(true);
      gfxPanel.add(m_yAxisPanel, BorderLayout.WEST);
      
      JPanel containerPanel = new JPanel();
      containerPanel.setLayout(new BorderLayout());
      containerPanel.add(gfxPanel, BorderLayout.CENTER);
      add(containerPanel, BorderLayout.WEST);
      
      JPanel rightHandToolsPanel = new JPanel(); //this panel contains the widgets to the right of the BoundaryPanel.
      rightHandToolsPanel.setLayout(new BoxLayout(rightHandToolsPanel, BoxLayout.PAGE_AXIS));
  
      rightHandToolsPanel.add(m_addRemovePointsPanel);
      
      JButton newWindowButton = new JButton("Open a new window"); //the button for spawning a new window for the program.
      //newWindowButton.setMaximumSize(new Dimension(100, 100));
      //newWindowButton.setPreferredSize(new Dimension(120, m_addRemovePointsPanel.getHeight()));
      newWindowButton.addActionListener(new ActionListener() {
      	public void actionPerformed(ActionEvent e) {
  		try {
  			Instances newTrainingData = null;
  			Classifier newClassifier = null;
  			if (m_trainingInstances != null)
  				newTrainingData = new Instances(m_trainingInstances);
  			if (m_classifier != null)
  				newClassifier = AbstractClassifier.makeCopy(m_classifier);
  			createNewVisualizerWindow(newClassifier, newTrainingData);
  		} catch (Exception ex) {  ex.printStackTrace();}
  	}
      });
      JPanel newWindowHolder = new JPanel();
      newWindowHolder.add(newWindowButton);
      rightHandToolsPanel.add(newWindowHolder);
      rightHandToolsPanel.add(tempPanel);
      rightHandToolsPanel.add(startPanel);
      
      containerPanel.add(rightHandToolsPanel, BorderLayout.EAST);
          
      /*add(m_boundaryPanel, BorderLayout.CENTER);
  
      m_xAxisPanel = new AxisPanel(false);
      add(m_xAxisPanel, BorderLayout.SOUTH);
      m_yAxisPanel = new AxisPanel(true);
      add(m_yAxisPanel, BorderLayout.WEST);*/
  
      m_startBut.setEnabled(false);
      m_startBut.addActionListener(new ActionListener() {
  	public void actionPerformed(ActionEvent e) {
  	  if (m_startBut.getText().equals("Start")) {
  	    if (m_trainingInstances != null && m_classifier != null) {
  		try {
  			
  			int BPSuccessCode = setUpBoundaryPanel(); //set up the boundary panel, find out if it was successful or not.
  			
  			if (BPSuccessCode == 1)
  				JOptionPane.showMessageDialog(null,"Error: Kernel Bandwidth can't be less than zero!");
  			else if (BPSuccessCode == 2) {
  				JOptionPane.showMessageDialog(null,"Error: Kernel Bandwidth must be less than the number of training instances!");
  			} else {
  				m_boundaryPanel.start();
  				m_startBut.setText("Stop");
  				setControlEnabledStatus(false);
  			}
  		} catch (Exception ex) {
  			ex.printStackTrace();
  		}
  	    }
  	  } else {
  	    m_boundaryPanel.stopPlotting();
  	    m_startBut.setText("Start");
  	    setControlEnabledStatus(true);
  	  }
  	}
        });
  
      m_boundaryPanel.addActionListener(new ActionListener() {
  	public void actionPerformed(ActionEvent e) {
  	  m_startBut.setText("Start");
  	  setControlEnabledStatus(true);
  	}
        });
  
      m_classPanel.addActionListener(new ActionListener() {
  	public void actionPerformed(ActionEvent e) {
  
  	  try {
  	    // save color vector to a file
  	    FastVector colors = m_boundaryPanel.getColors();
  	    FileOutputStream fos = new FileOutputStream("colors.ser");
  	    ObjectOutputStream oos = new ObjectOutputStream(fos);
  	    oos.writeObject(colors);
  	    oos.flush();
  	    oos.close();
  	  } catch (Exception ex) {}
  
  	  m_boundaryPanel.replot();
  	  
  	}
        });
        
      //set up a mouse listener for the boundary panel.
      m_boundaryPanel.addMouseListener(new MouseAdapter() {
      	public void mouseClicked(MouseEvent e) {
  // 		System.err.println("boundary panel mouseClick " + e.getX() + " " + e.getY());
  		if (m_trainingInstances != null) {
  			if (m_startBut.getText().equals("Stop")) //we are plotting
  				return;
  		
  			if (m_addPointsButton.isSelected()) {//we are in add mode
  				double classVal = 0;
  				boolean validInput = true;
  				if (m_trainingInstances.attribute(m_classAttBox.getSelectedIndex()).isNominal()) //class is nominal
  					classVal = (double)m_classValueSelector.getSelectedIndex();
  				else {
  					String indexStr = "";
  					try {					
  						indexStr = (String)m_classValueSelector.getSelectedItem();
  						classVal = Double.parseDouble(indexStr);
  					} catch (Exception ex) {
  						if (indexStr == null) indexStr = "";
  						JOptionPane.showMessageDialog(null,"Error adding a point: \"" + indexStr + "\""
  							+ " is not a valid class value.");
  						validInput = false;
  					}
  				}
  				//System.err.println("classVal is " + classVal);
  				if (validInput)
  					m_boundaryPanel.addTrainingInstanceFromMouseLocation(e.getX(), e.getY(), m_classAttBox.getSelectedIndex(), classVal);
  			}
  			else { //remove mode
  				m_boundaryPanel.removeTrainingInstanceFromMouseLocation(e.getX(), e.getY());
  			}
  			try{ plotTrainingData(); } catch (Exception ex) {} //jimmy
  			m_xAxisPanel.repaint(0,0,0,m_xAxisPanel.getWidth(), m_xAxisPanel.getHeight());
      			m_yAxisPanel.repaint(0,0,0,m_yAxisPanel.getWidth(), m_yAxisPanel.getHeight());
  		}
  	}
      });
    }
      
    /**
     * Set the enabled status of the controls
     *
     * @param status a <code>boolean</code> value
     */
    private void setControlEnabledStatus(boolean status) {
      m_classAttBox.setEnabled(status);
      m_xAttBox.setEnabled(status);
      m_yAttBox.setEnabled(status);
      m_regionSamplesText.setEnabled(status);
      m_generatorSamplesText.setEnabled(status);
      m_kernelBandwidthText.setEnabled(status);
      m_plotTrainingData.setEnabled(status);
      removeAllButton.setEnabled(status);
      m_classValueSelector.setEnabled(status);
      m_addPointsButton.setEnabled(status);
      m_removePointsButton.setEnabled(status);
      m_FileChooser.setEnabled(status);
      chooseButton.setEnabled(status);
    }
  
    /**
     * Set a classifier to use
     *
     * @param newClassifier the classifier to use
     * @exception Exception if an error occurs
     */
    public void setClassifier(Classifier newClassifier) throws Exception {
  
      m_classifier = newClassifier;
      
      try {
  	int classIndex = m_classAttBox.getSelectedIndex();
  	
  	if ((m_classifier != null) && (m_trainingInstances != null) &&
  		(m_trainingInstances.attribute(classIndex).isNominal())) {
  		m_startBut.setEnabled(true);
  	}
  	else
  		m_startBut.setEnabled(false);
      } catch (Exception e) {}
      
    }
    
    /** Sets up the bounds on our x and y axes to fit the dataset.
        Also repaints the x and y axes.
    */
    private void computeBounds() {
    
      m_boundaryPanel.computeMinMaxAtts(); //delegate to the BoundaryPanel
    
      String xName = (String)m_xAttBox.getSelectedItem();
      if (xName == null) {
        return;
      }
      xName = Utils.removeSubstring(xName, "X: ");
      xName = Utils.removeSubstring(xName, " (Num)");
      String yName = (String)m_yAttBox.getSelectedItem();
      yName = Utils.removeSubstring(yName, "Y: ");
      yName = Utils.removeSubstring(yName, " (Num)");
  
      m_xIndex = -1;
      m_yIndex = -1;
      for (int i = 0; i < m_trainingInstances.numAttributes(); i++) {
        if (m_trainingInstances.attribute(i).name().equals(xName)) {
  	m_xIndex = i;
        } 
        if (m_trainingInstances.attribute(i).name().equals(yName)) {
  	m_yIndex = i;
        }
      }
      
      m_minX = m_boundaryPanel.getMinXBound();
      m_minY = m_boundaryPanel.getMinYBound();
      m_maxX = m_boundaryPanel.getMaxXBound();
      m_maxY = m_boundaryPanel.getMaxYBound();
      //System.err.println("setting bounds to " + m_minX + " " + m_minY + " " + m_maxX + " " + m_maxY);
      m_xAxisPanel.repaint(0,0,0,m_xAxisPanel.getWidth(), m_xAxisPanel.getHeight());
      m_yAxisPanel.repaint(0,0,0,m_yAxisPanel.getWidth(), m_yAxisPanel.getHeight());
    }
  
    /**
     * Get the training instances
     *
     * @return the training instances
     */
    public Instances getInstances() {
      return m_trainingInstances;
    }
  
    /**
     * Set the training instances
     *
     * @param inst the instances to use
     */
    public void setInstances(Instances inst) throws Exception {
      if (inst == null) {
      	m_trainingInstances = inst;
      	m_classPanel.setInstances(m_trainingInstances);
  	return;
      }
      
      // count the number of numeric attributes
      int numCount = 0;
      for (int i = 0; i < inst.numAttributes(); i++) {
        if (inst.attribute(i).isNumeric()) {
          numCount++;
        }
      }
      
      if (numCount < 2) {
        JOptionPane.showMessageDialog(null,"We need at least two numeric " +
        		"attributes in order to visualize!");
        return;
      }
          
      m_trainingInstances = inst;
      m_classPanel.setInstances(m_trainingInstances);
      // setup combo boxes
      String [] classAttNames = new String [m_trainingInstances.numAttributes()];
      final Vector xAttNames = new Vector();
      Vector yAttNames = new Vector();
  
      for (int i = 0; i < m_trainingInstances.numAttributes(); i++) {
        classAttNames[i] = m_trainingInstances.attribute(i).name();
        String type = " (" + Attribute.typeToStringShort(m_trainingInstances.attribute(i)) + ")";
        classAttNames[i] += type;
        if (m_trainingInstances.attribute(i).isNumeric()) {
  	xAttNames.addElement("X: "+classAttNames[i]);
  	yAttNames.addElement("Y: "+classAttNames[i]);
        }
      }
  
      m_classAttBox.setModel(new DefaultComboBoxModel(classAttNames));
      m_xAttBox.setModel(new DefaultComboBoxModel(xAttNames));
      m_yAttBox.setModel(new DefaultComboBoxModel(yAttNames));
      if (xAttNames.size() > 1) {
        m_yAttBox.setSelectedIndex(1);
      }
      
      m_classAttBox.addActionListener(new ActionListener() {
  	public void actionPerformed(ActionEvent e) {
  	  configureForClassAttribute();
  	}
        });
  
      m_xAttBox.addItemListener(new ItemListener() {
  	public void itemStateChanged(ItemEvent e) {
  	  if (e.getStateChange() == ItemEvent.SELECTED) {
  /*	    if (xAttNames.size() > 1) {
  	      if (m_xAttBox.getSelectedIndex() == 
  		  m_yAttBox.getSelectedIndex()) {
  		m_xAttBox.setSelectedIndex((m_xAttBox.getSelectedIndex() + 1) %
  					   xAttNames.size());
  	      }
  	    } */
  	    computeBounds();
  	    repaint();
  	    try{ plotTrainingData(); } catch (Exception ex) {ex.printStackTrace();} //jimmy	    
  	  }
  	}
        });
  
      m_yAttBox.addItemListener(new ItemListener() {
  	public void itemStateChanged(ItemEvent e) {
  	  if (e.getStateChange() == ItemEvent.SELECTED) {
  /*	    if (xAttNames.size() > 1) {
  	      if (m_yAttBox.getSelectedIndex() == 
  		  m_xAttBox.getSelectedIndex()) {
  		m_yAttBox.setSelectedIndex((m_yAttBox.getSelectedIndex() + 1) %
  					   xAttNames.size());
  	      }
  	    } */
  	    computeBounds();
  	    repaint();
  	    try{ plotTrainingData(); } catch (Exception ex) {ex.printStackTrace();}
  	  }
  	}
        });
      
      if (classAttNames.length > 0)
        m_classAttBox.setSelectedIndex(classAttNames.length - 1); //select last attribute as class by default.  -jimmy
        
      //set up the add points selector combo box
      setUpClassValueSelectorCB();
      
      configureForClassAttribute();
      
      m_classPanel.setCindex(m_classAttBox.getSelectedIndex());
      plotTrainingData();      
      computeBounds();
      revalidate();
      repaint();
      
      if (getTopLevelAncestor() instanceof java.awt.Window) {
        ((java.awt.Window)getTopLevelAncestor()).pack();
      }
    }
    
    /** Set up the combo box that chooses which class values to use when adding data points.
    */
    private void setUpClassValueSelectorCB() {
      m_classValueSelector.removeAllItems();
      int classAttribute = m_classAttBox.getSelectedIndex();
      //System.err.println(m_trainingInstances.numClasses() + " classes");
      m_trainingInstances.setClassIndex(classAttribute);
      if (m_trainingInstances.attribute(classAttribute).isNominal()) {
      	m_classValueSelector.setEditable(false);
      	for (int i = 0; i < /*m_trainingInstances.numDistinctValues(classAttribute)*/m_trainingInstances.numClasses(); i++)
      		m_classValueSelector.insertItemAt(m_trainingInstances.attribute(classAttribute).value(i) , i);
  	m_classValueSelector.setSelectedIndex(0);
      }
      else {
      	m_classValueSelector.setEditable(true);
      }
    }
    
    /**
     * Set up the class values combo boxes
     */
    private void configureForClassAttribute() {
      int classIndex = m_classAttBox.getSelectedIndex();
      if (classIndex >= 0) {
        // see if this is a nominal attribute
        if (!m_trainingInstances.attribute(classIndex).isNominal() || m_classifier == null) {
  	m_startBut.setEnabled(false);
        } else {
  	m_startBut.setEnabled(true);
        }
        // set up class colours
  	FastVector colors = new FastVector();
  	if (!m_trainingInstances.attribute(m_classAttBox.getSelectedIndex()).isNominal()) //this if by jimmy
  	{
  		for (int i = 0; i < BoundaryPanel.DEFAULT_COLORS.length; i++)
  			colors.addElement(BoundaryPanel.DEFAULT_COLORS[i]);
  	}
  	else {
  		for (int i = 0; i < 
  		m_trainingInstances.attribute(classIndex).numValues(); i++) {
  			colors.addElement(BoundaryPanel.
  				DEFAULT_COLORS[i % BoundaryPanel.DEFAULT_COLORS.length]);
  // 			m_classPanel.setColours(colors);	  
  // 			m_boundaryPanel.setColors(colors);
  		}
  	}
  	m_classPanel.setColours(colors); //jimmy
  	m_boundaryPanel.setColors(colors);
     }
    }
    
      
    /**
     * Queries the user for a file to load instances from, then loads the
     * instances in a background process. This is done in the IO
     * thread, and an error message is popped up if the IO thread is busy.
     */
    public void setInstancesFromFileQ() {
      
  //     if (m_IOThread == null) {
        int returnVal = m_FileChooser.showOpenDialog(this);
        if (returnVal == JFileChooser.APPROVE_OPTION) {
  	File selected = m_FileChooser.getSelectedFile();
  	
  	try
  	{
  	java.io.Reader r = new java.io.BufferedReader(
  				new java.io.FileReader(selected));
  	Instances i = new Instances(r);
  	setInstances(i);
  	
  	//dataFileLabel.setText(selected.getName());
  	String relationName = i.relationName();
  	String truncatedN = relationName;
  	if (relationName.length() > 25) {
  	  truncatedN = relationName.substring(0, 25) + "...";
  	}
  	dataFileLabel.setText(truncatedN);
  	dataFileLabel.setToolTipText(relationName);
  	} catch (Exception e)
  	{
  		JOptionPane.showMessageDialog(this,"Can't load at this time,\n"
  				    + "currently busy with other IO",
  				    "Load Instances",
  				    JOptionPane.WARNING_MESSAGE);
  		    e.printStackTrace();
  	
  	}
        }
    }
    
    /** Sets up the BoundaryPanel object so that it is ready for plotting.
     * @return an error code:<br/>
     *		0 - SUCCESS<br/>
     *		1 - ERROR - Kernel bandwidth < 0<br/>
     *		2 - ERROR - Kernel bandwidth >= number of training instances.
     */
    public int setUpBoundaryPanel() throws Exception {
    	int returner = 0; //OK code.
    	int tempSamples = m_numberOfSamplesFromEachRegion;
  		try {
  		  tempSamples = 
  		    Integer.parseInt(m_regionSamplesText.getText().trim());
  		} catch (Exception ex) {
  		  m_regionSamplesText.setText(""+tempSamples);
  		}
  		m_numberOfSamplesFromEachRegion = tempSamples;
  		m_boundaryPanel.
  		  setNumSamplesPerRegion(tempSamples);
  
  		tempSamples = m_generatorSamplesBase;
  		try {
  		  tempSamples = 
  		    Integer.parseInt(m_generatorSamplesText.getText().trim());
  		} catch (Exception ex) {
  		  m_generatorSamplesText.setText(""+tempSamples);
  		}
  		m_generatorSamplesBase = tempSamples;
  		m_boundaryPanel.setGeneratorSamplesBase((double)tempSamples);
  
  		tempSamples = m_kernelBandwidth;
  		try {
  		  tempSamples = 
  		    Integer.parseInt(m_kernelBandwidthText.getText().trim());
  		} catch (Exception ex) {
  		  m_kernelBandwidthText.setText(""+tempSamples);
  		}
  		m_kernelBandwidth = tempSamples;
  		m_dataGenerator.setKernelBandwidth(tempSamples);
  		
  		if (m_kernelBandwidth < 0)	returner = 1;
  		if (m_kernelBandwidth >= m_trainingInstances.numInstances())	returner = 2;
  
  		m_trainingInstances.
  		  setClassIndex(m_classAttBox.getSelectedIndex());
  		m_boundaryPanel.setClassifier(m_classifier);
  		m_boundaryPanel.setTrainingData(m_trainingInstances);
  		m_boundaryPanel.setXAttribute(m_xIndex);
  		m_boundaryPanel.setYAttribute(m_yIndex);
  		m_boundaryPanel.
  		  setPlotTrainingData(m_plotTrainingData.isSelected());
  
  	return returner;
    }
    
    /** Plots the training data on-screen.  Also does all of the setup required 
     *  for this to work.
    */
    public void plotTrainingData() throws Exception {
    	m_boundaryPanel.initialize();
   	setUpBoundaryPanel();
  	computeBounds();
  	m_boundaryPanel.plotTrainingData();
    }
    
    /** Stops the plotting thread.
    */
    public void stopPlotting() {
    	m_boundaryPanel.stopPlotting();
    }
    
    /**
     * Sets whether System.exit gets called when no more windows are open.
     * 
     * @param value	if TRUE then a System.exit call is ossued after the 
     * 			last window gets closed.
     */
    public static void setExitIfNoWindowsOpen(boolean value) {
      m_ExitIfNoWindowsOpen = value;
    }
    
    /**
     * Gets whether System.exit gets called after the last window gets closed
     * 
     * @return		TRUE if System.exit gets called after last window
     * 			got closed.
     */
    public static boolean getExitIfNoWindowsOpen() {
      return m_ExitIfNoWindowsOpen;
    }
    
    /** Creates a new GUI window with all of the BoundaryVisualizer trappings,
     *  @param classifier The classifier to use in the new window.  May be null.
     *  @param instances  The dataset to visualize on in the new window.  May be null.
     */
    public static void createNewVisualizerWindow(Classifier classifier, Instances instances) throws Exception {
        m_WindowCount++;
    
        final javax.swing.JFrame jf = 
  	new javax.swing.JFrame("Weka classification boundary visualizer");
        jf.getContentPane().setLayout(new BorderLayout());
        final BoundaryVisualizer bv = new BoundaryVisualizer();
        jf.getContentPane().add(bv, BorderLayout.CENTER);
        jf.setSize(bv.getMinimumSize());
        jf.addWindowListener(new java.awt.event.WindowAdapter() {
  	  public void windowClosing(java.awt.event.WindowEvent e) {
  	    m_WindowCount--;
  	    bv.stopPlotting();
  	    jf.dispose();
  	    if ((m_WindowCount == 0) && m_ExitIfNoWindowsOpen) {
  		System.exit(0);
  	    }
  	  }
  	});
  
        jf.pack();
        jf.setVisible(true);
        jf.setResizable(false);
        
        if (classifier == null)
        	bv.setClassifier(null);
        else {
  	bv.setClassifier(classifier);
  	bv.m_classifierEditor.setValue(classifier);
        }
        
        if (instances == null)
        	bv.setInstances(null);
        else
        {
  	bv.setInstances(instances);
  	
  	try{
  		bv.dataFileLabel.setText(instances.relationName());
  		bv.plotTrainingData();
  		bv.m_classPanel.setCindex(bv.m_classAttBox.getSelectedIndex());
  		bv.repaint(0,0,0,bv.getWidth(), bv.getHeight());
  	} catch (Exception ex) {}
        }
    
    }
  
    /**
     * Main method for testing this class
     *
     * @param args a <code>String[]</code> value
     */
    public static void main(String [] args) {
      weka.core.logging.Logger.log(weka.core.logging.Logger.Level.INFO, "Logging started");
      try {
      	if (args.length < 2) {
  		createNewVisualizerWindow(null, null);
  	}
  	else {
  		String [] argsR = null;
  		if (args.length > 2) {
  			argsR = new String [args.length-2];
  			for (int j = 2; j < args.length; j++) {
  			argsR[j-2] = args[j];
  			}
  		}
  		Classifier c = AbstractClassifier.forName(args[1], argsR);
  		
  		System.err.println("Loading instances from : "+args[0]);
  		java.io.Reader r = new java.io.BufferedReader(
  					new java.io.FileReader(args[0]));
  		Instances i = new Instances(r);
  	
  		createNewVisualizerWindow(c, i);
  	}
        
      } catch (Exception ex) {
        ex.printStackTrace();
      }
    }
  }
  
  
  