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.
   */
  
  /*
   *    StripChart.java
   *    Copyright (C) 2002 University of Waikato, Hamilton, New Zealand
   *
   */
  
  package weka.gui.beans;
  
  import weka.core.Instance;
  import weka.core.Instances;
  import weka.gui.visualize.PrintableComponent;
  import weka.gui.visualize.VisualizeUtils;
  
  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.Image;
  import java.beans.EventSetDescriptor;
  import java.io.IOException;
  import java.io.ObjectInputStream;
  import java.util.Enumeration;
  import java.util.LinkedList;
  import java.util.Random;
  import java.util.Vector;
  
  import javax.swing.BorderFactory;
  import javax.swing.JComponent;
  import javax.swing.JFrame;
  import javax.swing.JPanel;
  import javax.swing.border.TitledBorder;
  
  /**
   * Bean that can display a horizontally scrolling strip chart. Can
   * display multiple plots simultaneously
   *
   * @author <a href="mailto:mhall@cs.waikato.ac.nz">Mark Hall</a>
   * @version $Revision: 7258 $
   */
  public class StripChart
    extends JPanel
    implements ChartListener, InstanceListener, Visible,
  	     BeanCommon, UserRequestAcceptor {
  
    /** for serialization */
    private static final long serialVersionUID = 1483649041577695019L;
  
    /** default colours for colouring lines */
    protected Color [] m_colorList = {Color.green,
  				    Color.red,
  				    Color.blue,
  				    Color.cyan,
  				    Color.pink,
  				    new Color(255, 0, 255),
  				    Color.orange,
  				    new Color(255, 0, 0),
  				    new Color(0, 255, 0),
  				    Color.white};
  
    /** the background color. */
    protected Color m_BackgroundColor;
    
    /** the color of the legend panel's border. */
    protected Color m_LegendPanelBorderColor;
    
    /**
     * Class providing a panel for the plot.
     */
    private class StripPlotter
      extends JPanel {
  
      /** for serialization. */
      private static final long serialVersionUID = -7056271598761675879L;
  
      public void paintComponent(Graphics g) {
        super.paintComponent(g);
        if (m_osi != null) {
  	g.drawImage(m_osi,0,0,this);
        }
      }
    }
  
    private transient JFrame m_outputFrame = null;
    private transient StripPlotter m_plotPanel = null;
  
    /**
     * The off screen image for rendering to.
     */
    private transient Image m_osi = null;
  
    /**
     * Width and height of the off screen image.
     */
    private int m_iheight;
    private int m_iwidth;
  
    /**
     * Max value for the y axis.
     */
    private double m_max = 1;
  
    /**
     * Min value for the y axis.
     */
    private double m_min = 0;
  
    /**
     * Scale update requested.
     */
    private boolean m_yScaleUpdate = false;
    private double m_oldMax;
    private double m_oldMin;
  
    private Font m_labelFont = new Font("Monospaced", Font.PLAIN, 10);
    private FontMetrics m_labelMetrics;
  
    //  private int m_plotCount = 0;
  
    private Vector m_legendText = new Vector();
  
    /**
     * Class providing a panel for displaying the y axis.
     */
    private class ScalePanel 
      extends JPanel {
  
      /** for serialization. */
      private static final long serialVersionUID = 6416998474984829434L;
  
      public void paintComponent(Graphics gx) {
        super.paintComponent(gx);
        if (m_labelMetrics == null) {
  	m_labelMetrics = gx.getFontMetrics(m_labelFont);
        }
        gx.setFont(m_labelFont);
        int hf = m_labelMetrics.getAscent();
        String temp = ""+m_max;
        gx.setColor(m_colorList[m_colorList.length-1]);
        gx.drawString(temp, 1, hf-2);
        temp = ""+(m_min + ((m_max - m_min)/2.0));
        gx.drawString(temp, 1, (this.getHeight() / 2)+(hf / 2));
        temp = ""+m_min;
        gx.drawString(temp, 1, this.getHeight()-1);
      }
    };
    
    /** the scale. */
    private ScalePanel m_scalePanel = new ScalePanel();
  
    /**
     * Class providing a panel for the legend.
     */
    private class LegendPanel 
      extends JPanel {
  
      /** for serialization. */
      private static final long serialVersionUID = 7713986576833797583L;
  
      public void paintComponent(Graphics gx) {
        super.paintComponent(gx);
  
        if (m_labelMetrics == null) {
  	m_labelMetrics = gx.getFontMetrics(m_labelFont);
        }
        int hf = m_labelMetrics.getAscent();
        int x = 10; int y = hf+15;
        gx.setFont(m_labelFont);
        for (int i = 0; i < m_legendText.size(); i++) {
  	String temp = (String)m_legendText.elementAt(i);
  	gx.setColor(m_colorList[(i % m_colorList.length)]);
  	gx.drawString(temp,x,y);
  	y+=hf;
        }
        StripChart.this.revalidate();
      }
    };
    
    /** the legend. */
    private LegendPanel m_legendPanel = new LegendPanel();
  
    /**
     * Holds the data to be plotted. Entries in the list are arrays of
     * y points.
     */
    private LinkedList m_dataList = new LinkedList();
    //  private double [] m_dataPoint = new double[1];
    private double [] m_previousY = new double[1];
  
    private transient Thread m_updateHandler;
  
    protected BeanVisual m_visual =
      new BeanVisual("StripChart",
  		   BeanVisual.ICON_PATH+"StripChart.gif",
  		   BeanVisual.ICON_PATH+"StripChart_animated.gif");
  
    private Object m_listenee = null;
    private transient weka.gui.Logger m_log = null;
  
    /**
     * Print x axis labels every m_xValFreq points
     */
    private int m_xValFreq = 500;
    private int m_xCount = 0;
  
    /**
     * Shift the plot by this many pixels every time a point is plotted
     */
    private int m_refreshWidth = 1;
  
    /**
     * Plot every m_refreshFrequency'th point
     */
    private int m_refreshFrequency = 5;
  
    /** the class responsible for printing */
    protected PrintableComponent m_Printer = null;
  
    public StripChart() {
  
      //    m_plotPanel.setBorder(BorderFactory.createEmptyBorder(5,5,5,5));
  
      setLayout(new BorderLayout());
      add(m_visual, BorderLayout.CENTER);
  
      // start a thread to handle plot updates
      initPlot();
    }
  
    /**
     * Set a custom (descriptive) name for this bean
     *
     * @param name the name to use
     */
    public void setCustomName(String name) {
      m_visual.setText(name);
    }
  
    /**
     * Get the custom (descriptive) name for this bean (if one has been set)
     *
     * @return the custom name (or the default name)
     */
    public String getCustomName() {
      return m_visual.getText();
    }
  
    /**
     * Global info for this bean
     *
     * @return a <code>String</code> value
     */
    public String globalInfo() {
      return "Visualize incremental classifier performance as a scrolling plot.";
    }
  
    /**
     * GUI Tip text
     *
     * @return a <code>String</code> value
     */
    public String xLabelFreqTipText() {
      return "Show x axis labels this often";
    }
  
    /**
     * Set the frequency for printing x label values
     *
     * @param freq an <code>int</code> value
     */
    public void setXLabelFreq(int freq) {
      m_xValFreq = freq;
      if (getGraphics() != null)
        setRefreshWidth();
    }
  
    /**
     * Get the frequency by which x axis values are printed
     *
     * @return an <code>int</code> value
     */
    public int getXLabelFreq() {
      return m_xValFreq;
    }
  
    /**
     * GUI Tip text
     *
     * @return a <code>String</code> value
     */
    public String refreshFreqTipText() {
      return "Plot every x'th data point";
    }
  
    /**
     * Set how often (in x axis points) to refresh the display
     *
     * @param freq an <code>int</code> value
     */
    public void setRefreshFreq(int freq) {
      m_refreshFrequency = freq;
      if (getGraphics() != null)
        setRefreshWidth();
    }
  
    /**
     * Get the refresh frequency
     *
     * @return an <code>int</code> value
     */
    public int getRefreshFreq() {
      return m_refreshFrequency;
    }
  
    private void setRefreshWidth() {
      m_refreshWidth = 1;
      if (m_labelMetrics == null) {
        getGraphics().setFont(m_labelFont);
        m_labelMetrics = getGraphics().getFontMetrics(m_labelFont);
      }
  
      int refWidth = m_labelMetrics.stringWidth("99000");
      // compute how often x label will be rendered
      int z = (getXLabelFreq() / getRefreshFreq());
      if (z < 1) {
        z = 1;
      }
  
      if (z * m_refreshWidth < refWidth+5) {
        m_refreshWidth *= (((refWidth+5) / z) + 1) ;
      }
    }
  
    /**
     * Provide some necessary initialization after object has
     * been deserialized.
     *
     * @param ois an <code>ObjectInputStream</code> value
     * @exception IOException if an error occurs
     * @exception ClassNotFoundException if an error occurs
     */
    private void readObject(ObjectInputStream ois)
      throws IOException, ClassNotFoundException {
      try {
        ois.defaultReadObject();
        initPlot();
        //      startHandler();
      } catch (Exception ex) {
        ex.printStackTrace();
      }
    }
  
    /**
     * Loads properties from properties file.
     * 
     * @see	KnowledgeFlowApp#BEAN_PROPERTIES
     */
    private void setProperties() {
      String 	key;
      String 	color;
  
      // background color
      key   = this.getClass().getName() + ".backgroundColour";
      color = KnowledgeFlowApp.BEAN_PROPERTIES.getProperty(key);
      m_BackgroundColor = Color.BLACK;
      if (color != null)
        m_BackgroundColor = VisualizeUtils.processColour(color, m_BackgroundColor);
  
      // legend color (border)
      key   = m_legendPanel.getClass().getName() + ".borderColour";
      color = KnowledgeFlowApp.BEAN_PROPERTIES.getProperty(key);
      m_LegendPanelBorderColor = Color.BLUE;
      if (color != null)
        m_LegendPanelBorderColor = VisualizeUtils.processColour(color, m_LegendPanelBorderColor);
    }
  
    private void initPlot() {
      setProperties();
      m_plotPanel = new StripPlotter();
      m_plotPanel.setBackground(m_BackgroundColor);
      m_scalePanel.setBackground(m_BackgroundColor);
      m_legendPanel.setBackground(m_BackgroundColor);
      m_xCount = 0;
    }
  
    private void startHandler() {
      if (m_updateHandler == null) {
        m_updateHandler = new Thread() {
  	  private double [] dataPoint;
  	  public void run() {
  	    while (true) {
  	      if (m_outputFrame != null) {
  		synchronized(m_dataList) {
  		  while(m_dataList.isEmpty()) {
  		  //		  while (m_dataList.empty()) {
  		    try {
  		      m_dataList.wait();
  		    } catch (InterruptedException ex) {
  		      return;
  		    }
  		  }
  		  dataPoint = (double [])m_dataList.remove(0);
  		  //dataPoint  = (double [])m_dataList.pop();
  		}
  		if (m_outputFrame != null) {
  		  StripChart.this.updateChart(dataPoint);
  		}
  	      }
  	    }
  	  }
  	};
        //      m_updateHandler.setPriority(Thread.MIN_PRIORITY);
        m_updateHandler.start();
      }
    }
  
    /**
     * Popup the chart panel
     */
    public void showChart() {
      if (m_outputFrame == null) {
        m_outputFrame = new JFrame("Strip Chart");
        m_outputFrame.getContentPane().setLayout(new BorderLayout());
        JPanel panel = new JPanel(new BorderLayout());
        new PrintableComponent(panel);
        m_outputFrame.getContentPane().add(panel, BorderLayout.CENTER);
        panel.add(m_legendPanel, BorderLayout.WEST);
        panel.add(m_plotPanel, BorderLayout.CENTER);
        panel.add(m_scalePanel, BorderLayout.EAST);
        m_legendPanel.setMinimumSize(new Dimension(100,getHeight()));
        m_legendPanel.setPreferredSize(new Dimension(100,getHeight()));
        m_scalePanel.setMinimumSize(new Dimension(30, getHeight()));
        m_scalePanel.setPreferredSize(new Dimension(30, getHeight()));
        Font lf = new Font("Monospaced", Font.PLAIN, 12);
        m_legendPanel.setBorder(BorderFactory.
  			      createTitledBorder(BorderFactory.
  				    createEtchedBorder(Color.gray,
  						       Color.darkGray),
  				    "Legend" ,
  				    TitledBorder.CENTER,
  				    TitledBorder.DEFAULT_POSITION, lf,
  				    m_LegendPanelBorderColor));
        m_outputFrame.addWindowListener(new java.awt.event.WindowAdapter() {
  	  public void windowClosing(java.awt.event.WindowEvent e) {
  	    if (m_updateHandler != null) {
  	      System.err.println("Interrupting");
  	      m_updateHandler.interrupt();
  	      m_updateHandler = null;
  	    }
  	    synchronized (m_dataList) {
  	      m_dataList = new LinkedList();
  	    }
  	    m_outputFrame.dispose();
  	    m_outputFrame = null;
  	  }
  	});
        m_outputFrame.pack();
        m_outputFrame.setSize(600,150);
        m_outputFrame.setResizable(false);
        m_outputFrame.setVisible(true);
        m_outputFrame.setAlwaysOnTop(true);
        //m_outputFrame.setLocationByPlatform(true);
        int iwidth = m_plotPanel.getWidth();
        int iheight = m_plotPanel.getHeight();
        m_osi = m_plotPanel.createImage(iwidth, iheight);
        Graphics m = m_osi.getGraphics();
        m.setColor(m_BackgroundColor);
        m.fillRect(0,0,iwidth,iheight);
        m_previousY[0] = -1;
        setRefreshWidth();
        if (m_updateHandler == null) {
  	System.err.println("Starting handler");
  	startHandler();
        }
      } else {
        m_outputFrame.toFront();
      }
    }
  
    private int convertToPanelY(double yval) {
      int height = m_plotPanel.getHeight();
      double temp = (yval - m_min) / (m_max - m_min);
      temp = temp * height;
      temp = height - temp;
      return (int)temp;
    }
  
    /**
     * Update the plot
     *
     * @param dataPoint contains y values to plot
     */
    protected void updateChart(double [] dataPoint) {
      if (m_previousY[0] == -1) {
        int iw = m_plotPanel.getWidth();
        int ih = m_plotPanel.getHeight();
        m_osi = m_plotPanel.createImage(iw, ih);
        Graphics m = m_osi.getGraphics();
        m.setColor(m_BackgroundColor);
        m.fillRect(0,0,iw,ih);
        m_previousY[0] = convertToPanelY(0);
        m_iheight = ih; m_iwidth = iw;
      }
  
      if (dataPoint.length-1 != m_previousY.length) {
        m_previousY = new double [dataPoint.length-1];
        //      m_plotCount = 0;
        for (int i = 0; i < dataPoint.length-1; i++) {
  	m_previousY[i] = convertToPanelY(0);
        }
      }
  
      Graphics osg = m_osi.getGraphics();
      Graphics g = m_plotPanel.getGraphics();
  
      osg.copyArea(m_refreshWidth,0,m_iwidth-m_refreshWidth,
  		 m_iheight,-m_refreshWidth,0);
      osg.setColor(m_BackgroundColor);
      osg.fillRect(m_iwidth-m_refreshWidth,0, m_iwidth, m_iheight);
  
      // paint the old scale onto the plot if a scale update has occured
      if (m_yScaleUpdate) {
        String maxVal = numToString(m_oldMax);
        String minVal = numToString(m_oldMin);
        String midVal = numToString((m_oldMax - m_oldMin) / 2.0);
        if (m_labelMetrics == null) {
  	m_labelMetrics = g.getFontMetrics(m_labelFont);
        }
        osg.setFont(m_labelFont);
        int wmx = m_labelMetrics.stringWidth(maxVal);
        int wmn = m_labelMetrics.stringWidth(minVal);
        int wmd = m_labelMetrics.stringWidth(midVal);
  
        int hf = m_labelMetrics.getAscent();
        osg.setColor(m_colorList[m_colorList.length-1]);
        osg.drawString(maxVal, m_iwidth-wmx, hf-2);
        osg.drawString(midVal, m_iwidth-wmd, (m_iheight / 2)+(hf / 2));
        osg.drawString(minVal, m_iwidth-wmn, m_iheight-1);
        m_yScaleUpdate = false;
      }
  
      double pos;
      for (int i = 0; i < dataPoint.length-1; i++) {
        osg.setColor(m_colorList[(i % m_colorList.length)]);
        pos = convertToPanelY(dataPoint[i]);
        osg.drawLine(m_iwidth-m_refreshWidth, (int)m_previousY[i],
  		   m_iwidth-1, (int)pos);
        m_previousY[i] = pos;
        if (dataPoint[dataPoint.length-1] % m_xValFreq == 0) {
  	// draw the actual y value onto the plot for this curve
  	String val = numToString(dataPoint[i]);
  	if (m_labelMetrics == null) {
  	  m_labelMetrics = g.getFontMetrics(m_labelFont);
  	}
  	int hf = m_labelMetrics.getAscent();
  	if (pos - hf < 0) {
  	  pos += hf;
  	}
  	int w = m_labelMetrics.stringWidth(val);
  	osg.setFont(m_labelFont);
  	osg.drawString(val, m_iwidth-w, (int)pos);
        }
      }
  
      // last element in the data point array contains the data point number
      if (dataPoint[dataPoint.length-1] % m_xValFreq == 0) {
  
        String xVal = ""+(int)dataPoint[dataPoint.length-1];
        osg.setColor(m_colorList[m_colorList.length-1]);
        int w = m_labelMetrics.stringWidth(xVal);
        osg.setFont(m_labelFont);
        osg.drawString(xVal, m_iwidth-w, m_iheight - 1);
      }
      g.drawImage(m_osi,0,0,m_plotPanel);
      //    System.err.println("Finished");
      //    m_plotCount++;
    }
  
    private static String numToString(double num) {
      int precision = 1;
      int whole = (int)Math.abs(num);
      double decimal = Math.abs(num) - whole;
      int nondecimal;
      nondecimal = (whole > 0)
        ? (int)(Math.log(whole) / Math.log(10))
        : 1;
  
      precision = (decimal > 0)
        ? (int)Math.abs(((Math.log(Math.abs(num)) /
  				      Math.log(10))))+2
        : 1;
      if (precision > 5) {
        precision = 1;
      }
  
      String numString = weka.core.Utils.doubleToString(num,
  						      nondecimal+1+precision
  						      ,precision);
  
      return numString;
    }
  
    ChartEvent m_ce = new ChartEvent(this);
    double [] m_dataPoint = null;
    public void acceptInstance(InstanceEvent e) {
      if (e.getStatus() == InstanceEvent.FORMAT_AVAILABLE) {
        Instances structure = e.getStructure();
        m_legendText = new Vector();
        m_max = 1.0;
        m_min = 0;
        int i = 0;
        for (i =0; i < structure.numAttributes(); i++) {
  	if (i > 10) {
  	  i--;
  	  break;
  	}
  	m_legendText.addElement(structure.attribute(i).name());
  	m_legendPanel.repaint();
  	m_scalePanel.repaint();
        }
        m_dataPoint = new double[i];
        m_xCount = 0;
        return;
      }
  
      // process data point
      Instance inst = e.getInstance();
      for (int i = 0; i < m_dataPoint.length; i++) {
        if (!inst.isMissing(i)) {
  	m_dataPoint[i] = inst.value(i);
        }
      }
      acceptDataPoint(m_dataPoint);
      m_xCount++;
    }
  
    /**
     * Accept a data point (encapsulated in a chart event) to plot
     *
     * @param e a <code>ChartEvent</code> value
     */
    public void acceptDataPoint(ChartEvent e) {
      if (e.getReset()) {
        m_xCount = 0;
        m_max = 1;
        m_min = 0;
      }
      if (m_outputFrame != null) {
        boolean refresh = false;
        if (e.getLegendText() != null & e.getLegendText() != m_legendText) {
  	m_legendText = e.getLegendText();
  	refresh = true;
        }
  
        if (e.getMin() != m_min || e.getMax() != m_max) {
  	m_oldMax = m_max; m_oldMin = m_min;
  	m_max = e.getMax();
  	m_min = e.getMin();
  	refresh = true;
  	m_yScaleUpdate = true;
        }
  
        if (refresh) {
  	m_legendPanel.repaint();
  	m_scalePanel.repaint();
        }
  
        acceptDataPoint(e.getDataPoint());
      }
      m_xCount++;
    }
  
    /**
     * Accept a data point to plot
     *
     * @param dataPoint a <code>double[]</code> value
     */
    public void acceptDataPoint(double [] dataPoint) {
  
      if (m_outputFrame != null && (m_xCount % m_refreshFrequency == 0 )) {
        double [] dp = new double[dataPoint.length+1];
        dp[dp.length-1] = m_xCount;
        System.arraycopy(dataPoint, 0, dp, 0, dataPoint.length);
        // check for out of scale values
        for (int i = 0; i < dataPoint.length; i++) {
  	if (dataPoint[i] < m_min) {
  	  m_oldMin = m_min; m_min = dataPoint[i];
  	  m_yScaleUpdate = true;
  	}
  
  	if (dataPoint[i] > m_max) {
  	  m_oldMax = m_max; m_max = dataPoint[i];
  	  m_yScaleUpdate = true;
  	}
        }
        if (m_yScaleUpdate) {
  	m_scalePanel.repaint();
  	m_yScaleUpdate = false;
        }
        synchronized(m_dataList) {
  	m_dataList.add(m_dataList.size(), dp);
  	//	m_dataList.push(dp);
  	m_dataList.notifyAll();
  	/*	if (m_dataList.size() != 0) {
  	  System.err.println("***** "+m_dataList.size());
  	  } */
  
  	//      System.err.println(m_xCount);
        }
      }
    }
  
    /**
     * Set the visual appearance of this bean
     *
     * @param newVisual a <code>BeanVisual</code> value
     */
    public void setVisual(BeanVisual newVisual) {
      m_visual = newVisual;
    }
  
    /**
     * Get the visual appearance of this bean
     */
    public BeanVisual getVisual() {
      return m_visual;
    }
  
    /**
     * Use the default visual appearance for this bean
     */
    public void useDefaultVisual() {
      m_visual.loadIcons(BeanVisual.ICON_PATH+"StripChart.gif",
  		       BeanVisual.ICON_PATH+"StripChart_animated.gif");
    }
  
    /**
     * Stop any processing that the bean might be doing.
     */
    public void stop() {
      // tell the listenee (upstream bean) to stop
      if (m_listenee instanceof BeanCommon) {
        ((BeanCommon)m_listenee).stop();
      }
    }
    
    /**
     * Returns true if. at this time, the bean is busy with some
     * (i.e. perhaps a worker thread is performing some calculation).
     * 
     * @return true if the bean is busy.
     */
    public boolean isBusy() {
      //return (m_updateHandler != null);
      return false;
    }
  
    /**
     * Set a logger
     *
     * @param logger a <code>weka.gui.Logger</code> value
     */
    public void setLog(weka.gui.Logger logger) {
      m_log = logger;
    }
  
    /**
     * Returns true if, at this time,
     * the object will accept a connection via the named event
     *
     * @param eventName the name of the event
     * @return true if the object will accept a connection
     */
    public boolean connectionAllowed(String eventName) {
      if (m_listenee == null) {
        return true;
      }
      return false;
    }
  
    /**
     * Returns true if, at this time,
     * the object will accept a connection according to the supplied
     * EventSetDescriptor
     *
     * @param esd the EventSetDescriptor
     * @return true if the object will accept a connection
     */
    public boolean connectionAllowed(EventSetDescriptor esd) {
      return connectionAllowed(esd.getName());
    }
  
    /**
     * Notify this object that it has been registered as a listener with
     * a source for recieving events described by the named event
     * This object is responsible for recording this fact.
     *
     * @param eventName the event
     * @param source the source with which this object has been registered as
     * a listener
     */
    public void connectionNotification(String eventName, Object source) {
      if (connectionAllowed(eventName)) {
        m_listenee = source;
      }
    }
  
    /**
     * Notify this object that it has been deregistered as a listener with
     * a source for named event. This object is responsible
     * for recording this fact.
     *
     * @param eventName the event
     * @param source the source with which this object has been registered as
     * a listener
     */
    public void disconnectionNotification(String eventName, Object source) {
      m_listenee = null;
    }
  
    /**
     * Describe <code>enumerateRequests</code> method here.
     *
     * @return an <code>Enumeration</code> value
     */
    public Enumeration enumerateRequests() {
      Vector newVector = new Vector(0);
      newVector.addElement("Show chart");
      return newVector.elements();
    }
  
    /**
     * Describe <code>performRequest</code> method here.
     *
     * @param request a <code>String</code> value
     * @exception IllegalArgumentException if an error occurs
     */
    public void performRequest(String request) {
      if (request.compareTo("Show chart") == 0) {
        showChart();
      } else {
        throw new
  	IllegalArgumentException(request
  				 + " not supported (StripChart)");
      }
    }
  
    /**
     * Tests out the StripChart from the command line
     *
     * @param args ignored
     */
    public static void main(String [] args) {
  
      try {
        final javax.swing.JFrame jf =
  	new javax.swing.JFrame("Weka Knowledge Flow : StipChart");
        jf.getContentPane().setLayout(new BorderLayout());
        final StripChart jd = new StripChart();
        jf.getContentPane().add(jd, BorderLayout.CENTER);
        jf.addWindowListener(new java.awt.event.WindowAdapter() {
  	public void windowClosing(java.awt.event.WindowEvent e) {
  	  jf.dispose();
  	  System.exit(0);
  	}
        });
        jf.pack();
        jf.setVisible(true);
        jd.showChart();
        Random r = new Random(1);
        for (int i = 0; i < 1020; i++) {
  	double [] pos = new double[1];
  	pos[0] = r.nextDouble();
  	jd.acceptDataPoint(pos);
        }
        System.err.println("Done sending data");
      } catch (Exception ex) {
        ex.printStackTrace();
        System.err.println(ex.getMessage());
      }
    }
  }
  