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.
   */
  
  /*
   *    AttributePanel.java
   *    Copyright (C) 1999 University of Waikato, Hamilton, New Zealand
   *
   */
  
  package weka.gui.visualize;
  
  import weka.core.Attribute;
  import weka.core.FastVector;
  import weka.core.Instances;
  
  import java.awt.BorderLayout;
  import java.awt.Color;
  import java.awt.Dimension;
  import java.awt.Graphics;
  import java.awt.GridBagConstraints;
  import java.awt.GridBagLayout;
  import java.awt.Insets;
  import java.awt.event.MouseAdapter;
  import java.awt.event.MouseEvent;
  
  import javax.swing.JPanel;
  import javax.swing.JScrollPane;
  
  /**
   * This panel displays one dimensional views of the attributes in a
   * dataset. Colouring is done on the basis of a column in the dataset or
   * an auxiliary array (useful for colouring cluster predictions).
   * 
   * @author Malcolm Ware (mfw4@cs.waikato.ac.nz)
   * @author Mark Hall (mhall@cs.waikato.ac.nz)
   * @version $Revision: 5086 $
   */
  public class AttributePanel
    extends JScrollPane {
  
    /** for serialization */
    private static final long serialVersionUID = 3533330317806757814L;
    
    /** The instances to be plotted */
    protected Instances m_plotInstances=null;
      
    /** Holds the min and max values of the colouring attributes */
    protected double m_maxC;
    protected double m_minC;
    protected int m_cIndex;
    protected int m_xIndex;
    protected int m_yIndex;
  
    /** The colour map to use for colouring points */
    protected FastVector m_colorList;
  
     /** default colours for colouring discrete class */
      protected Color [] m_DefaultColors = {Color.blue,
  					Color.red,
  					Color.green,
  					Color.cyan,
  					Color.pink,
  					new Color(255, 0, 255),
  					Color.orange,
  					new Color(255, 0, 0),
  					new Color(0, 255, 0),
  					Color.white};
      
    /**
     *  If set, it allows this panel to avoid setting a color in
     * the color list that is equal to the background color
     */
    protected Color m_backgroundColor = null; 
  
    /** The list of things listening to this panel */
    protected FastVector m_Listeners = new FastVector();
  
    /** Holds the random height for each instance. */
    protected int[] m_heights;
    //protected Color[] colors_array;
  
    /** The container window for the attribute bars, and also where the
     * X,Y or B get printed.
     */ 
    protected JPanel m_span=null;
  
    /** The default colour to use for the background of the bars if
        a colour is not defined in Visualize.props */
    protected Color m_barColour=Color.black;
      
    /** inner inner class used for plotting the points 
     * into a bar for a particular attribute. 
     */
    protected class AttributeSpacing
      extends JPanel {
  
      /** for serialization */
      private static final long serialVersionUID = 7220615894321679898L;
  
      /** The min and max values for this attribute. */
      protected double m_maxVal;
      protected double m_minVal;
  
      /** The attribute itself. */
      protected Attribute m_attrib;
        
      /** The index for this attribute. */
      protected int m_attribIndex;
        
      /** The x position of each point. */
      protected int[] m_cached;
      //note for m_cached, if you wanted to speed up the drawing algorithm
      // and save memory, the system could be setup to drop out any
      // of the instances not being drawn (you would need to find a new way
      //of matching the height however).
  
      /** A temporary array used to strike any instances that would be 
       * drawn redundantly.
       */
      protected boolean[][] m_pointDrawn;
        
      /** Used to determine if the positions need to be recalculated. */
      protected int m_oldWidth=-9000;
  
      /** The container window for the attribute bars, and also where the
       * X,Y or B get printed.
       */
        
      /**
       * This constructs the bar with the specified attribute and
       * sets its index to be used for selecting by the mouse.
       * @param a The attribute this bar represents.
       * @param aind The index of this bar.
       */
      public AttributeSpacing(Attribute a, int aind) {
        m_attrib = a;
        m_attribIndex = aind;
        this.setBackground(m_barColour);
        this.setPreferredSize(new Dimension(0, 20));
        this.setMinimumSize(new Dimension(0, 20));
        m_cached = new int[m_plotInstances.numInstances()];
  	
        //this will only get allocated if m_plotInstances != null
        //this is used to determine the min and max values for plotting
        double min=Double.POSITIVE_INFINITY;
        double max=Double.NEGATIVE_INFINITY;
        double value;
        if (m_plotInstances.attribute(m_attribIndex).isNominal()) {
  	m_minVal = 0;
  	m_maxVal = m_plotInstances.attribute(m_attribIndex).numValues()-1;
        } else {
  	for (int i=0;i<m_plotInstances.numInstances();i++) {
  	  if (!m_plotInstances.instance(i).isMissing(m_attribIndex)) {
  	    value = m_plotInstances.instance(i).value(m_attribIndex);
  	    if (value < min) {
  	      min = value;
  	    }
  	    if (value > max) {
  	      max = value;
  	    }
  	  }
  	}
  	m_minVal = min; m_maxVal = max;
  	if (min == max) {
  	  m_maxVal += 0.05;
  	  m_minVal -= 0.05;
  	}
        }
  	
        this.addMouseListener(new MouseAdapter() {
  	  public void mouseClicked(MouseEvent e) {
  	    if ((e.getModifiers() & e.BUTTON1_MASK) == e.BUTTON1_MASK) {
  	      setX(m_attribIndex);
  	      if (m_Listeners.size() > 0) {
  		for (int i=0;i<m_Listeners.size();i++) {
  		  AttributePanelListener l = 
  		    (AttributePanelListener)(m_Listeners.elementAt(i));
  		  l.attributeSelectionChange(new AttributePanelEvent(true,
  					     false, m_attribIndex));
  		}
  	      }
  	    }
  	    else {
  	      //put it on the y axis
  	      setY(m_attribIndex);
  	      if (m_Listeners.size() > 0) {
  		for (int i=0;i<m_Listeners.size();i++) {
  		  AttributePanelListener l = 
  		    (AttributePanelListener)(m_Listeners.elementAt(i));
  		  l.attributeSelectionChange(new AttributePanelEvent(false,
  					     true, m_attribIndex));
  		}
  	      }
  	    }
  	  }
  	});
      }
        
      /**
       * Convert an raw x value to Panel x coordinate.
       * @param val the raw x value
       * @return an x value for plotting in the panel.
       */
      private double convertToPanel(double val) {
        double temp = (val - m_minVal)/(m_maxVal - m_minVal);
        double temp2 = temp * (this.getWidth() - 10);
  	
        return temp2 + 4; 
      }
        
      /**
       * paints all the visible instances to the panel , and recalculates
       * their position if need be.
       * @param gx The graphics context.
       */
      public void paintComponent(Graphics gx) {
        super.paintComponent(gx);
        int xp, yp, h;
        h = this.getWidth();
        if (m_plotInstances != null 
  	  && m_plotInstances.numAttributes() > 0
  	  && m_plotInstances.numInstances() > 0) {
  
  	if (m_oldWidth != h) {
  	  m_pointDrawn = new boolean[h][20];
  	  for (int noa = 0; noa < m_plotInstances.numInstances(); noa++) {
  	    if (!m_plotInstances.instance(noa).isMissing(m_attribIndex)
  		&& !m_plotInstances.instance(noa).isMissing(m_cIndex)) {
  	      m_cached[noa] = (int)convertToPanel(m_plotInstances.
  						  instance(noa).
  						  value(m_attribIndex));
  		
  	      if (m_pointDrawn[m_cached[noa] % h][m_heights[noa]]) {
  		m_cached[noa] = -9000;
  	      }
  	      else {
  		m_pointDrawn[m_cached[noa]%h][m_heights[noa]] = true;
  	      }
  		
  	    }
  	    else {
  	      m_cached[noa] = -9000; //this value will not happen 
  	      //so it is safe
  	    }
  	  }
  	  m_oldWidth = h;
  	}
  	  
  	if (m_plotInstances.attribute(m_cIndex).isNominal()) {
  	  for (int noa = 0; noa < m_plotInstances.numInstances(); noa++) {
  	      
  	    if (m_cached[noa] != -9000) {
  	      xp = m_cached[noa];
  	      yp = m_heights[noa];
  	      if (m_plotInstances.attribute(m_attribIndex).
  		  isNominal()) {
  		xp += (int)(Math.random() * 5) - 2;
  	      }
  	      int ci = (int)m_plotInstances.instance(noa).value(m_cIndex);
  
  	      gx.setColor((Color)m_colorList.elementAt
  			  (ci % m_colorList.size()));
  	      gx.drawRect(xp, yp, 1, 1);
  	    }
  	  }
  	}
  	else {
  	  double r;
  	  for (int noa = 0; noa < m_plotInstances.numInstances(); noa++) {
  	    if (m_cached[noa] != -9000) {		  
  		
  	      r = (m_plotInstances.instance(noa).value(m_cIndex) 
  		   - m_minC) / (m_maxC - m_minC);
  
  	      r = (r * 240) + 15;
  
  	      gx.setColor(new Color((int)r,150,(int)(255-r)));
  		
  	      xp = m_cached[noa];
  	      yp = m_heights[noa];
  	      if (m_plotInstances.attribute(m_attribIndex).
  		  isNominal()) {
  		xp += (int)(Math.random() * 5) - 2;
  	      }
  	      gx.drawRect(xp, yp, 1, 1);
  	    }
  	  }
  	}
        } 
      }
    }   
      
    /**
     * Set the properties for the AttributePanel
     */
    private void setProperties() {
      if (VisualizeUtils.VISUALIZE_PROPERTIES != null) {
        String thisClass = this.getClass().getName();
        String barKey = thisClass+".barColour";
        
        String barC = VisualizeUtils.VISUALIZE_PROPERTIES.
  	      getProperty(barKey);
        if (barC == null) {
  	/*
  	System.err.println("Warning: no configuration property found in "
  			   +VisualizeUtils.PROPERTY_FILE
  			   +" for "+barKey);
  	*/
        } else {
  	//System.err.println("Setting attribute bar colour to: "+barC);
  	m_barColour = VisualizeUtils.processColour(barC, m_barColour);
        }
      }
    }
    
    public AttributePanel() {
      this(null);
    }
   
    /**
     * This constructs an attributePanel.
     */
    public AttributePanel(Color background) {
      m_backgroundColor = background;
      
      setProperties();
      this.setBackground(Color.blue);
      setVerticalScrollBarPolicy(VERTICAL_SCROLLBAR_ALWAYS);
      m_colorList = new FastVector(10);
  
      for (int noa = m_colorList.size(); noa < 10; noa++) {
        Color pc = m_DefaultColors[noa % 10];
        int ija =  noa / 10;
        ija *= 2; 
        for (int j=0;j<ija;j++) {
  	pc = pc.darker();
        }
        
        m_colorList.addElement(pc);
      }
    }
  
    /**
     * Add a listener to the list of things listening to this panel
     * @param a the listener to notify when attribute bars are clicked on
     */
    public void addAttributePanelListener(AttributePanelListener a) {
      m_Listeners.addElement(a);
    }
    
    /**
     * Set the index of the attribute by which to colour the data. Updates
     * the number of entries in the colour list if there are more values
     * for this new attribute than previous ones.
     * @param c the index of the attribute to colour on
     * @param h maximum value of this attribute
     * @param l minimum value of this attribute
     */
    public void setCindex(int c, double h, double l) {
      m_cIndex = c;
      m_maxC = h;
      m_minC = l;
      
      if (m_span != null) {
        if (m_plotInstances.numAttributes() > 0 &&
  	  m_cIndex < m_plotInstances.numAttributes()) {
  	if (m_plotInstances.attribute(m_cIndex).isNominal()) {
  	  if (m_plotInstances.attribute(m_cIndex).numValues() > 
  	    m_colorList.size()) {
  	    extendColourMap();
  	  }
  	}
        }
        this.repaint();
      }
    }
  
    /**
     * Set the index of the attribute by which to colour the data. Updates
     * the number of entries in the colour list if there are more values
     * for this new attribute than previous ones.
     * @param c the index of the attribute to colour on
     */
    public void setCindex(int c) {
      m_cIndex = c;
      /*    m_maxC = h;
  	  m_minC = l; */
  
      if (m_span != null) {
        if (m_cIndex < m_plotInstances.numAttributes() && 
  	  m_plotInstances.attribute(m_cIndex).isNumeric()) {
  	double min=Double.POSITIVE_INFINITY;
  	double max=Double.NEGATIVE_INFINITY;
  	double value;
  
  	for (int i=0;i<m_plotInstances.numInstances();i++) {
  	  if (!m_plotInstances.instance(i).isMissing(m_cIndex)) {
  	    value = m_plotInstances.instance(i).value(m_cIndex);
  	    if (value < min) {
  	      min = value;
  	    }
  	    if (value > max) {
  	      max = value;
  	    }
  	  }
  	}
      
  	m_minC = min; m_maxC = max;
        } else {
  	if (m_plotInstances.attribute(m_cIndex).numValues() > 
  	    m_colorList.size()) {
  	  extendColourMap();
  	}
        }
      
        this.repaint();
      }
    }
  
    /**
     * Adds more colours to the colour list
     */
    private void extendColourMap() {
      if (m_plotInstances.attribute(m_cIndex).isNominal()) {
        for (int i = m_colorList.size(); 
  	   i < m_plotInstances.attribute(m_cIndex).numValues();
  	   i++) {
  	Color pc = m_DefaultColors[i % 10];
  	int ija =  i / 10;
  	ija *= 2; 
  	for (int j=0;j<ija;j++) {
  	  pc = pc.brighter();
  	}
  	
  	if (m_backgroundColor != null) {
  	  pc = Plot2D.checkAgainstBackground(pc, m_backgroundColor);
  	}
  
  	m_colorList.addElement(pc);
        }
      }
    }
  
    /**
     * Sets a list of colours to use for colouring data points
     * @param cols a list of java.awt.Color
     */
    public void setColours(FastVector cols) {
      m_colorList = cols;
    }
    
    protected void setDefaultColourList(Color[] list) {
      m_DefaultColors = list;
    }
  
    /** 
     * This sets the instances to be drawn into the attribute panel
     * @param ins The instances.
     */
    public void setInstances(Instances ins) throws Exception {
      if (ins.numAttributes() > 512) {
        throw new Exception("Can't display more than 512 attributes!");
      }
  
      if (m_span == null) {
        m_span = new JPanel() {
  	  private static final long serialVersionUID = 7107576557995451922L;
  	  
  	  public void paintComponent(Graphics gx) {
  	    super.paintComponent(gx);
  	    gx.setColor(Color.red);
  	    if (m_yIndex != m_xIndex) {
  	      gx.drawString("X", 5, m_xIndex * 20 + 16);
  	      gx.drawString("Y", 5, m_yIndex * 20 + 16);
  	    }
  	    else {
  	      gx.drawString("B", 5, m_xIndex * 20 + 16);
  	    }
  	  }
  	};
      }
  
      m_span.removeAll();
      m_plotInstances = ins;
      if (ins.numInstances() > 0 && ins.numAttributes() > 0) {
        JPanel padder = new JPanel();
        JPanel padd2 = new JPanel();
        
        /*    if (m_splitListener != null) {
  	    m_plotInstances.randomize(new Random());
  	    } */
  
        m_heights = new int[ins.numInstances()];
  
        m_cIndex = ins.numAttributes() - 1;
        for (int noa = 0; noa < ins.numInstances(); noa++) {
  	m_heights[noa] = (int)(Math.random() * 19);
        }
        m_span.setPreferredSize(new Dimension(m_span.getPreferredSize().width, 
  					    (m_cIndex + 1) * 20));
        m_span.setMaximumSize(new Dimension(m_span.getMaximumSize().width, 
  					  (m_cIndex + 1) * 20));
        AttributeSpacing tmp;
        
        GridBagLayout gb = new GridBagLayout();
        GridBagLayout gb2 = new GridBagLayout();
        GridBagConstraints constraints = new GridBagConstraints();
        
  
  
        padder.setLayout(gb);
        m_span.setLayout(gb2);
        constraints.anchor = GridBagConstraints.CENTER;
        constraints.gridx=0;constraints.gridy=0;constraints.weightx=5;
        constraints.fill = GridBagConstraints.HORIZONTAL;
        constraints.gridwidth=1;constraints.gridheight=1;
        constraints.insets = new Insets(0, 0, 0, 0);
        padder.add(m_span, constraints);
        constraints.gridx=0;constraints.gridy=1;constraints.weightx=5;
        constraints.fill = GridBagConstraints.BOTH;
        constraints.gridwidth=1;constraints.gridheight=1;constraints.weighty=5;
        constraints.insets = new Insets(0, 0, 0, 0);
        padder.add(padd2, constraints);
        constraints.weighty=0;
        setViewportView(padder);
        //getViewport().setLayout(null);
        //m_span.setMinimumSize(new Dimension(100, (m_cIndex + 1) * 24));
        //m_span.setSize(100, (m_cIndex + 1) * 24);
        constraints.anchor = GridBagConstraints.CENTER;
        constraints.gridx=0;constraints.gridy=0;constraints.weightx=5;
        constraints.fill = GridBagConstraints.HORIZONTAL;
        constraints.gridwidth=1;constraints.gridheight=1;constraints.weighty=5;
        constraints.insets = new Insets(2,20,2,4);
  
        for (int noa = 0; noa < ins.numAttributes(); noa++) {
  	tmp = new AttributeSpacing(ins.attribute(noa), noa);
  	 
  	constraints.gridy = noa;
  	m_span.add(tmp, constraints);
        }
      }
    }
      
    /**
     * shows which bar is the current x attribute.
     * @param x The attributes index.
     */
    public void setX(int x) {
      if (m_span != null) {
        m_xIndex = x;
        m_span.repaint();
      }
    }
      
    /**
     * shows which bar is the current y attribute.
     * @param y The attributes index.
     */
    public void setY(int y) {
      if (m_span != null) {
        m_yIndex = y;
        m_span.repaint();
      }
    }
  
    /**
     * Main method for testing this class.
     * @param args first argument should be an arff file. Second argument
     * can be an optional class col
     */
    public static void main(String [] args) {
      try {
        if (args.length < 1) {
  	System.err.println("Usage : weka.gui.visualize.AttributePanel "
  			   +"<dataset> [class col]");
  	System.exit(1);
        }
        final javax.swing.JFrame jf = 
  	new javax.swing.JFrame("Weka Explorer: Attribute");
        jf.setSize(100,100);
        jf.getContentPane().setLayout(new BorderLayout());
        final AttributePanel p2 = new AttributePanel();
        p2.addAttributePanelListener(new AttributePanelListener() {
  	  public void attributeSelectionChange(AttributePanelEvent e) {
  	    if (e.m_xChange) {
  	      System.err.println("X index changed to : "+e.m_indexVal);
  	    } else {
  	      System.err.println("Y index changed to : "+e.m_indexVal);
  	    }
  	  }
  	});
        jf.getContentPane().add(p2, BorderLayout.CENTER);
        jf.addWindowListener(new java.awt.event.WindowAdapter() {
  	  public void windowClosing(java.awt.event.WindowEvent e) {
  	    jf.dispose();
  	    System.exit(0);
  	  }
  	});
        if (args.length >= 1) {
  	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);
  	i.setClassIndex(i.numAttributes()-1);
  	p2.setInstances(i);
        }
        if (args.length > 1) {
  	p2.setCindex((Integer.parseInt(args[1]))-1);
        } else {
  	p2.setCindex(0);
        }
        jf.setVisible(true);
      } catch (Exception ex) {
         ex.printStackTrace();
         System.err.println(ex.getMessage());
       }
    }
  }
  