Package weka.gui.treevisualizer

Source Code of weka.gui.treevisualizer.TreeVisualizer

/*
*    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.
*/

/*
*    TreeVisualizer.java
*    Copyright (C) 1999 University of Waikato, Hamilton, New Zealand
*
*/

package weka.gui.treevisualizer;

import weka.core.Instances;
import weka.gui.visualize.PrintablePanel;
import weka.gui.visualize.VisualizePanel;

import java.awt.Color;
import java.awt.Container;
import java.awt.Dimension;
import java.awt.Font;
import java.awt.FontMetrics;
import java.awt.Graphics;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.ItemEvent;
import java.awt.event.ItemListener;
import java.awt.event.MouseEvent;
import java.awt.event.MouseListener;
import java.awt.event.MouseMotionListener;
import java.io.FileReader;
import java.io.IOException;
import java.io.StringReader;

import javax.swing.BorderFactory;
import javax.swing.ButtonGroup;
import javax.swing.JDialog;
import javax.swing.JFrame;
import javax.swing.JMenu;
import javax.swing.JMenuItem;
import javax.swing.JOptionPane;
import javax.swing.JPopupMenu;
import javax.swing.JRadioButton;
import javax.swing.JRadioButtonMenuItem;
import javax.swing.JTextField;
import javax.swing.Timer;

/**
* Class for displaying a Node structure in Swing. <p>
*
* To work this class simply create an instance of it.<p>
*
* Assign it to a window or other such object.<p>
*
* Resize it to the desired size.<p>
*
*
* When using the Displayer hold the left mouse button to drag the
* tree around. <p>
*
* Click the left mouse button with ctrl to shrink the size of the tree
* by half. <p>
*
* Click and drag with the left mouse button and shift to draw a box,
* when the left mouse button is released the contents of the box
* will be magnified
* to fill the screen. <p> <p>
*
* Click the right mouse button to bring up a menu. <p>
* Most options are self explanatory.<p>
*
* Select Auto Scale to set the tree to it's optimal display size.
*
* @author Malcolm Ware (mfw4@cs.waikato.ac.nz)
* @version $Revision: 1.13 $
*/
public class TreeVisualizer
  extends PrintablePanel
  implements MouseMotionListener, MouseListener, ActionListener, ItemListener {

  /** for serialization */
  private static final long serialVersionUID = -8668637962504080749L;

  /** The placement algorithm for the Node structure. */
  private NodePlace m_placer; 

  /** The top Node. */
  private Node m_topNode;
 
  /** The postion of the view relative to the tree. */
  private Dimension m_viewPos;       

  /** The size of the tree in pixels. */
  private Dimension m_viewSize;     
 
  /** The font used to display the tree. */
  private Font m_currentFont;       

  /** The size information for the current font. */
  private FontMetrics m_fontSize;   

  /** The number of Nodes in the tree. */
  private int m_numNodes;

  /** The number of levels in the tree. */
  private int m_numLevels;    

  /** An array with the Nodes sorted into it and display information
   * about the Nodes. */
  private NodeInfo[] m_nodes;

  /** An array with the Edges sorted into it and display information
   * about the Edges. */
  private EdgeInfo[] m_edges;
                    
  /** A timer to keep the frame rate constant. */
  private Timer m_frameLimiter;     
                        
  /** Describes the action the user is performing. */
  private int m_mouseState;          

  /** A variable used to tag the start pos of a user action. */
  private Dimension m_oldMousePos;

  /** A variable used to tag the most current point of a user action. */
  private Dimension m_newMousePos;

  /** A variable used to determine for the clicked method if any other
   * mouse state has already taken place. */
  private boolean m_clickAvailable;

  /** A variable used to remember the desired view pos. */
  private Dimension m_nViewPos;    

  /** A variable used to remember the desired tree size. */
  private Dimension m_nViewSize;      

  /** The number of frames left to calculate. */
  private int m_scaling;        

  /** A right (or middle) click popup menu. */
  private JPopupMenu m_winMenu;

  /** An option on the win_menu */
  private JMenuItem m_topN;

  /** An option on the win_menu*/
  private JMenuItem m_fitToScreen;

  /** An option on the win_menu */
  private JMenuItem m_autoScale;

  /** A ub group on the win_menu */
  private JMenu m_selectFont;

  /** A grouping for the font choices */
  private ButtonGroup m_selectFontGroup;

  /** A font choice. */
  private JRadioButtonMenuItem m_size24;

  /** A font choice. */
  private JRadioButtonMenuItem m_size22;

  /** A font choice. */
  private JRadioButtonMenuItem m_size20;

  /** A font choice. */
  private JRadioButtonMenuItem m_size18;

  /** A font choice. */
  private JRadioButtonMenuItem m_size16;

  /** A font choice. */
  private JRadioButtonMenuItem m_size14;

  /** A font choice. */
  private JRadioButtonMenuItem m_size12;

  /** A font choice. */
  private JRadioButtonMenuItem m_size10;

  /** A font choice. */
  private JRadioButtonMenuItem m_size8;

  /** A font choice. */
  private JRadioButtonMenuItem m_size6;

  /** A font choice. */
  private JRadioButtonMenuItem m_size4;

  /** A font choice. */
  private JRadioButtonMenuItem m_size2;

  /** A font choice. */
  private JRadioButtonMenuItem m_size1;

  /** An option on the win menu. */
  private JMenuItem m_accept;

  /** A right or middle click popup menu for nodes. */
  private JPopupMenu m_nodeMenu;

  /** A visualize choice for the node, may not be available. */
  private JMenuItem m_visualise;

  /**
   * An add children to Node choice, This is only available if the tree
   * display has a treedisplay listerner added to it.
   */
  private JMenuItem m_addChildren;

  /** Similar to add children but now it removes children. */
  private JMenuItem m_remChildren;

  /** Use this to have J48 classify this node. */
  private JMenuItem m_classifyChild;
 
  /** Use this to dump the instances from this node to the vis panel. */
  private JMenuItem m_sendInstances;

  /** The subscript for the currently selected node (this is an internal
   * thing, so the user is unaware of this). */
  private int m_focusNode;

  /**
   * The Node the user is currently focused on , this is similar to
   * focus node except that it is used by other
   * classes rather than this one.
   */
  private int m_highlightNode;
 
  /* A pointer to this tree's classifier if a classifier is using it. */
  //private UserClassifier classer;
  private TreeDisplayListener m_listener;

  private JTextField m_searchString;
  private JDialog m_searchWin;
  private JRadioButton m_caseSen;

  ///////////////////

  //this is the event fireing stuff


  /**
   * Constructs Displayer to display a tree provided in a dot format.
   * Uses the NodePlacer to place the Nodes.
   * @param tdl listener
   * @param dot string containing the dot representation of the tree to
   * display
   * @param p the algorithm to be used to position the nodes.
   */
  public TreeVisualizer(TreeDisplayListener tdl, String dot, NodePlace p) {
    super();
   
    //generate the node structure in here
    setBorder(BorderFactory.createTitledBorder("Tree View"));
    m_listener = tdl;

    TreeBuild builder = new TreeBuild();
   
    Node n = null;
    NodePlace arrange = new PlaceNode2();
    n = builder.create(new StringReader(dot));
    //    System.out.println(n.getCount(n, 0));
    //if the size needs to be automatically alocated I will do it here
    m_highlightNode = 5;
    m_topNode = n;
    m_placer = p;
    m_placer.place(m_topNode);
    m_viewPos = new Dimension(0, 0);    //will be adjusted
    m_viewSize = new Dimension(800, 600);   //I allocate this now so that
    //the tree will be visible
    //when the panel is enlarged

    m_nViewPos = new Dimension(0, 0);           
    m_nViewSize = new Dimension(800, 600);         
                                     
    m_scaling = 0;
   
    m_numNodes = m_topNode.getCount(m_topNode,0);   //note the second
    //argument must be a zero, this is a
    //recursive function

    m_numLevels = m_topNode.getHeight(m_topNode,0);
 
    m_nodes = new NodeInfo[m_numNodes];
    m_edges = new EdgeInfo[m_numNodes-1];
   

    arrayFill(m_topNode, m_nodes, m_edges);
   
    changeFontSize(12);

    m_mouseState = 0;
    m_oldMousePos = new Dimension(0, 0);
    m_newMousePos = new Dimension(0, 0);
    m_frameLimiter = new Timer(120, this);



    m_winMenu = new JPopupMenu();
    m_topN = new JMenuItem("Center on Top Node");           //note to change
    //language change this line
    m_topN.setActionCommand("Center on Top Node");          //but not this one,
    //same for all menu items
    m_fitToScreen = new JMenuItem("Fit to Screen");
    m_fitToScreen.setActionCommand("Fit to Screen");
    //unhide = new JMenuItem("Unhide all Nodes");
    m_selectFont = new JMenu("Select Font");
    m_selectFont.setActionCommand("Select Font");
    m_autoScale = new JMenuItem("Auto Scale");
    m_autoScale.setActionCommand("Auto Scale");
    m_selectFontGroup = new ButtonGroup();
   
    m_accept = new JMenuItem("Accept The Tree");
    m_accept.setActionCommand("Accept The Tree");
   
    m_winMenu.add(m_topN);
    m_winMenu.addSeparator();
    m_winMenu.add(m_fitToScreen);
    m_winMenu.add(m_autoScale);
    m_winMenu.addSeparator();
    //m_winMenu.add(unhide);
    m_winMenu.addSeparator();
    m_winMenu.add(m_selectFont);
    m_winMenu.addSeparator();

    if (m_listener != null) {
      m_winMenu.add(m_accept);
    }
   
    m_topN.addActionListener(this);
    m_fitToScreen.addActionListener(this);
    //unhide.addActionListener(this);
    m_autoScale.addActionListener(this);
    m_accept.addActionListener(this);
       
    m_size24 = new JRadioButtonMenuItem("Size 24",false);//,select_font_group);
    m_size22 = new JRadioButtonMenuItem("Size 22",false);//,select_font_group);
    m_size20 = new JRadioButtonMenuItem("Size 20",false);//,select_font_group);
    m_size18 = new JRadioButtonMenuItem("Size 18",false);//,select_font_group);
    m_size16 = new JRadioButtonMenuItem("Size 16",false);//,select_font_group);
    m_size14 = new JRadioButtonMenuItem("Size 14",false);//,select_font_group);
    m_size12 = new JRadioButtonMenuItem("Size 12",true);//,select_font_group);
    m_size10 = new JRadioButtonMenuItem("Size 10",false);//,select_font_group);
    m_size8 = new JRadioButtonMenuItem("Size 8",false);//,select_font_group);
    m_size6 = new JRadioButtonMenuItem("Size 6",false);//,select_font_group);
    m_size4 = new JRadioButtonMenuItem("Size 4",false);//,select_font_group);
    m_size2 = new JRadioButtonMenuItem("Size 2",false);//,select_font_group);
    m_size1 = new JRadioButtonMenuItem("Size 1",false);//,select_font_group);

    m_size24.setActionCommand("Size 24");//,select_font_group);
    m_size22.setActionCommand("Size 22");//,select_font_group);
    m_size20.setActionCommand("Size 20");//,select_font_group);
    m_size18.setActionCommand("Size 18");//,select_font_group);
    m_size16.setActionCommand("Size 16");//,select_font_group);
    m_size14.setActionCommand("Size 14");//,select_font_group);
    m_size12.setActionCommand("Size 12");//,select_font_group);
    m_size10.setActionCommand("Size 10");//,select_font_group);
    m_size8.setActionCommand("Size 8");//,select_font_group);
    m_size6.setActionCommand("Size 6");//,select_font_group);
    m_size4.setActionCommand("Size 4");//,select_font_group);
    m_size2.setActionCommand("Size 2");//,select_font_group);
    m_size1.setActionCommand("Size 1");//,select_font_group);
   
   
    m_selectFontGroup.add(m_size24);
    m_selectFontGroup.add(m_size22);
    m_selectFontGroup.add(m_size20);
    m_selectFontGroup.add(m_size18);
    m_selectFontGroup.add(m_size16);
    m_selectFontGroup.add(m_size14);
    m_selectFontGroup.add(m_size12);
    m_selectFontGroup.add(m_size10);
    m_selectFontGroup.add(m_size8);
    m_selectFontGroup.add(m_size6);
    m_selectFontGroup.add(m_size4);
    m_selectFontGroup.add(m_size2);
    m_selectFontGroup.add(m_size1);

   
    m_selectFont.add(m_size24);
    m_selectFont.add(m_size22);
    m_selectFont.add(m_size20);
    m_selectFont.add(m_size18);
    m_selectFont.add(m_size16);
    m_selectFont.add(m_size14);
    m_selectFont.add(m_size12);
    m_selectFont.add(m_size10);
    m_selectFont.add(m_size8);
    m_selectFont.add(m_size6);
    m_selectFont.add(m_size4);
    m_selectFont.add(m_size2);
    m_selectFont.add(m_size1);


    m_size24.addItemListener(this);
    m_size22.addItemListener(this);
    m_size20.addItemListener(this);
    m_size18.addItemListener(this);
    m_size16.addItemListener(this);
    m_size14.addItemListener(this);
    m_size12.addItemListener(this);
    m_size10.addItemListener(this);
    m_size8.addItemListener(this);
    m_size6.addItemListener(this);
    m_size4.addItemListener(this);
    m_size2.addItemListener(this);
    m_size1.addItemListener(this);

    /*
      search_string = new JTextField(22);
      search_win = new JDialog();
      case_sen = new JRadioButton("Case Sensitive");



      search_win.getContentPane().setLayout(null);
      search_win.setSize(300, 200);
      search_win.getContentPane().add(search_string);
      search_win.getContentPane().add(case_sen);

      search_string.setLocation(50, 70);
      case_sen.setLocation(50, 120);
      case_sen.setSize(100, 24);
      search_string.setSize(100, 24);
      //search_string.setVisible(true);
      //case_sen.setVisible(true);

      //search_win.setVisible(true);
    */

    m_nodeMenu = new JPopupMenu();
    /* A visualize choice for the node, may not be available. */
    m_visualise = new JMenuItem("Visualize The Node");
    m_visualise.setActionCommand("Visualize The Node");
    m_visualise.addActionListener(this);
    m_nodeMenu.add(m_visualise);
  
    if (m_listener != null) {
      m_remChildren = new JMenuItem("Remove Child Nodes");
      m_remChildren.setActionCommand("Remove Child Nodes");
      m_remChildren.addActionListener(this);
      m_nodeMenu.add(m_remChildren);
     
     
      m_classifyChild = new JMenuItem("Use Classifier...");
      m_classifyChild.setActionCommand("classify_child");
      m_classifyChild.addActionListener(this);
      m_nodeMenu.add(m_classifyChild);
     
      /*m_sendInstances = new JMenuItem("Add Instances To Viewer");
      m_sendInstances.setActionCommand("send_instances");
      m_sendInstances.addActionListener(this);
      m_nodeMenu.add(m_sendInstances); */
     
    }
   
    m_focusNode = -1;
    m_highlightNode = -1;
   
    addMouseMotionListener(this);
    addMouseListener(this);
    //repaint();
    //frame_limiter.setInitialDelay();
    m_frameLimiter.setRepeats(false);
    m_frameLimiter.start();
  }
 
  /**
   * Constructs Displayer with the specified Node as the top
   * of the tree, and uses the NodePlacer to place the Nodes.
   * @param tdl listener.
   * @param n the top Node of the tree to be displayed.
   * @param p the algorithm to be used to position the nodes.
   */ 
  public TreeVisualizer(TreeDisplayListener tdl, Node n, NodePlace p) {
    super();
   
    //if the size needs to be automatically alocated I will do it here
    setBorder(BorderFactory.createTitledBorder("Tree View"));
    m_listener = tdl;
    m_topNode = n;
    m_placer = p;
    m_placer.place(m_topNode);
    m_viewPos = new Dimension(0, 0);    //will be adjusted
    m_viewSize = new Dimension(800, 600);   //I allocate this now so that
    //the tree will be visible
    //when the panel is enlarged

    m_nViewPos = new Dimension(0, 0);           
    m_nViewSize = new Dimension(800, 600);         
                                     
    m_scaling = 0;
   
    m_numNodes = m_topNode.getCount(m_topNode,0);   //note the second argument
    //must be a zero, this is a
    //recursive function

    m_numLevels = m_topNode.getHeight(m_topNode,0);
 
    m_nodes = new NodeInfo[m_numNodes];
    m_edges = new EdgeInfo[m_numNodes-1];

    arrayFill(m_topNode, m_nodes, m_edges);
   
    changeFontSize(12);

    m_mouseState = 0;
    m_oldMousePos = new Dimension(0, 0);
    m_newMousePos = new Dimension(0, 0);
    m_frameLimiter = new Timer(120, this);





    m_winMenu = new JPopupMenu();
    m_topN = new JMenuItem("Center on Top Node");           //note to change
    //language change this line
    m_topN.setActionCommand("Center on Top Node");          //but not this
    //one, same for all menu items
    m_fitToScreen = new JMenuItem("Fit to Screen");
    m_fitToScreen.setActionCommand("Fit to Screen");
    //unhide = new JMenuItem("Unhide all Nodes");
    m_selectFont = new JMenu("Select Font");
    m_selectFont.setActionCommand("Select Font");
    m_autoScale = new JMenuItem("Auto Scale");
    m_autoScale.setActionCommand("Auto Scale");
    m_selectFontGroup = new ButtonGroup();
   
    m_accept = new JMenuItem("Accept The Tree");
    m_accept.setActionCommand("Accept The Tree");
   
    m_winMenu.add(m_topN);
    m_winMenu.addSeparator();
    m_winMenu.add(m_fitToScreen);
    m_winMenu.add(m_autoScale);
    m_winMenu.addSeparator();
    //m_winMenu.add(unhide);
    m_winMenu.addSeparator();
    m_winMenu.add(m_selectFont);
    m_winMenu.addSeparator();

    if (m_listener != null) {
      m_winMenu.add(m_accept);
    }
   
    m_topN.addActionListener(this);
    m_fitToScreen.addActionListener(this);
    //unhide.addActionListener(this);
    m_autoScale.addActionListener(this);
    m_accept.addActionListener(this);
       
    m_size24 = new JRadioButtonMenuItem("Size 24",false);//,select_font_group);
    m_size22 = new JRadioButtonMenuItem("Size 22",false);//,select_font_group);
    m_size20 = new JRadioButtonMenuItem("Size 20",false);//,select_font_group);
    m_size18 = new JRadioButtonMenuItem("Size 18",false);//,select_font_group);
    m_size16 = new JRadioButtonMenuItem("Size 16",false);//,select_font_group);
    m_size14 = new JRadioButtonMenuItem("Size 14",false);//,select_font_group);
    m_size12 = new JRadioButtonMenuItem("Size 12",true);//,select_font_group);
    m_size10 = new JRadioButtonMenuItem("Size 10",false);//,select_font_group);
    m_size8 = new JRadioButtonMenuItem("Size 8",false);//,select_font_group);
    m_size6 = new JRadioButtonMenuItem("Size 6",false);//,select_font_group);
    m_size4 = new JRadioButtonMenuItem("Size 4",false);//,select_font_group);
    m_size2 = new JRadioButtonMenuItem("Size 2",false);//,select_font_group);
    m_size1 = new JRadioButtonMenuItem("Size 1",false);//,select_font_group);

    m_size24.setActionCommand("Size 24");//,select_font_group);
    m_size22.setActionCommand("Size 22");//,select_font_group);
    m_size20.setActionCommand("Size 20");//,select_font_group);
    m_size18.setActionCommand("Size 18");//,select_font_group);
    m_size16.setActionCommand("Size 16");//,select_font_group);
    m_size14.setActionCommand("Size 14");//,select_font_group);
    m_size12.setActionCommand("Size 12");//,select_font_group);
    m_size10.setActionCommand("Size 10");//,select_font_group);
    m_size8.setActionCommand("Size 8");//,select_font_group);
    m_size6.setActionCommand("Size 6");//,select_font_group);
    m_size4.setActionCommand("Size 4");//,select_font_group);
    m_size2.setActionCommand("Size 2");//,select_font_group);
    m_size1.setActionCommand("Size 1");//,select_font_group);



   
   
    m_selectFontGroup.add(m_size24);
    m_selectFontGroup.add(m_size22);
    m_selectFontGroup.add(m_size20);
    m_selectFontGroup.add(m_size18);
    m_selectFontGroup.add(m_size16);
    m_selectFontGroup.add(m_size14);
    m_selectFontGroup.add(m_size12);
    m_selectFontGroup.add(m_size10);
    m_selectFontGroup.add(m_size8);
    m_selectFontGroup.add(m_size6);
    m_selectFontGroup.add(m_size4);
    m_selectFontGroup.add(m_size2);
    m_selectFontGroup.add(m_size1);



   
    m_selectFont.add(m_size24);
    m_selectFont.add(m_size22);
    m_selectFont.add(m_size20);
    m_selectFont.add(m_size18);
    m_selectFont.add(m_size16);
    m_selectFont.add(m_size14);
    m_selectFont.add(m_size12);
    m_selectFont.add(m_size10);
    m_selectFont.add(m_size8);
    m_selectFont.add(m_size6);
    m_selectFont.add(m_size4);
    m_selectFont.add(m_size2);
    m_selectFont.add(m_size1);


    m_size24.addItemListener(this);
    m_size22.addItemListener(this);
    m_size20.addItemListener(this);
    m_size18.addItemListener(this);
    m_size16.addItemListener(this);
    m_size14.addItemListener(this);
    m_size12.addItemListener(this);
    m_size10.addItemListener(this);
    m_size8.addItemListener(this);
    m_size6.addItemListener(this);
    m_size4.addItemListener(this);
    m_size2.addItemListener(this);
    m_size1.addItemListener(this);




    /*
      search_string = new JTextField(22);
      search_win = new JDialog();
      case_sen = new JRadioButton("Case Sensitive");



      search_win.getContentPane().setLayout(null);
      search_win.setSize(300, 200);
      search_win.getContentPane().add(search_string);
      search_win.getContentPane().add(case_sen);

      search_string.setLocation(50, 70);
      case_sen.setLocation(50, 120);
      case_sen.setSize(100, 24);
      search_string.setSize(100, 24);
      //search_string.setVisible(true);
      //case_sen.setVisible(true);

      search_win.setVisible(true);
    */


    m_nodeMenu = new JPopupMenu();
    /* A visualize choice for the node, may not be available. */
    m_visualise = new JMenuItem("Visualize The Node");
    m_visualise.setActionCommand("Visualize The Node");
    m_visualise.addActionListener(this);
    m_nodeMenu.add(m_visualise);

    if (m_listener != null) {
      m_remChildren = new JMenuItem("Remove Child Nodes");
      m_remChildren.setActionCommand("Remove Child Nodes");
      m_remChildren.addActionListener(this);
      m_nodeMenu.add(m_remChildren);
     
      m_classifyChild = new JMenuItem("Use Classifier...");
      m_classifyChild.setActionCommand("classify_child");
      m_classifyChild.addActionListener(this);
      m_nodeMenu.add(m_classifyChild);
     
      m_sendInstances = new JMenuItem("Add Instances To Viewer");
      m_sendInstances.setActionCommand("send_instances");
      m_sendInstances.addActionListener(this);
      m_nodeMenu.add(m_sendInstances);
     
     
    }
 
    m_focusNode = -1;
    m_highlightNode = -1;
   

    addMouseMotionListener(this);
    addMouseListener(this);

 
    //repaint();

    //frame_limiter.setInitialDelay();
    m_frameLimiter.setRepeats(false);
    m_frameLimiter.start();
  }

  /**
   * Fits the tree to the current screen size. Call this after
   * window has been created to get the entrire tree to be in view
   * upon launch.
   */
  public void fitToScreen() {

    getScreenFit(m_viewPos, m_viewSize);
    repaint();
  }

  /**
   * Calculates the dimensions needed to fit the entire tree into view.
   */
  private void getScreenFit(Dimension np, Dimension ns) {

    int leftmost = 1000000, rightmost = -1000000;
    int leftCenter = 1000000, rightCenter = -1000000, rightNode = 0;
    int highest = -1000000, highTop = -1000000;
    for (int noa = 0; noa < m_numNodes; noa++) {
      calcScreenCoords(noa);
      if (m_nodes[noa].m_center - m_nodes[noa].m_side < leftmost) {
  leftmost = m_nodes[noa].m_center - m_nodes[noa].m_side;
      }
      if (m_nodes[noa].m_center < leftCenter) {
  leftCenter = m_nodes[noa].m_center;
      }
     
      if (m_nodes[noa].m_center + m_nodes[noa].m_side > rightmost) {
  rightmost = m_nodes[noa].m_center + m_nodes[noa].m_side;   
      }
      if (m_nodes[noa].m_center > rightCenter) {
  rightCenter = m_nodes[noa].m_center;
  rightNode = noa;
      }
  if (m_nodes[noa].m_top + m_nodes[noa].m_height > highest) {
    highest = m_nodes[noa].m_top + m_nodes[noa].m_height;
  }
  if (m_nodes[noa].m_top > highTop) {
    highTop = m_nodes[noa].m_top;
  }
    }
   
    ns.width = getWidth();
    ns.width -= leftCenter - leftmost + rightmost - rightCenter + 30;
    ns.height = getHeight() - highest + highTop - 40;
   
    if (m_nodes[rightNode].m_node.getCenter() != 0
  && leftCenter != rightCenter) {
  ns.width /= m_nodes[rightNode].m_node.getCenter();
    }
    if (ns.width < 10)
      {
  ns.width = 10;
      }
    if (ns.height < 10)
      {
  ns.height = 10;
      }
   
    np.width = (leftCenter - leftmost + rightmost - rightCenter) / 2 + 15;
    np.height = (highest - highTop) / 2 + 20;
  }

  /**
   * Performs the action associated with the ActionEvent.
   *
   * @param e the action event.
   */
  public void actionPerformed(ActionEvent e) {
   
    //JMenuItem m = (JMenuItem)e.getSource();
   
    if (e.getActionCommand() == null) {
      if (m_scaling == 0) {
  repaint();
      }
      else {
  animateScaling(m_nViewPos, m_nViewSize, m_scaling);
      }
    }
    else if (e.getActionCommand().equals("Fit to Screen")) {
     
      Dimension np = new Dimension();
      Dimension ns = new Dimension();

      getScreenFit(np, ns);

      animateScaling(np, ns, 10);
     
    }
    else if (e.getActionCommand().equals("Center on Top Node")) {
     
      int tpx = (int)(m_topNode.getCenter() * m_viewSize.width);   //calculate
      //the top nodes postion but don't adjust for where
      int tpy = (int)(m_topNode.getTop() * m_viewSize.height);     //view is
     
     
     
      Dimension np = new Dimension(getSize().width / 2 - tpx,
           getSize().width / 6 - tpy);
     
      animateScaling(np, m_viewSize, 10);
     
    }
    else if (e.getActionCommand().equals("Auto Scale")) {
      autoScale()//this will figure the best scale value
      //keep the focus on the middle of the screen and call animate
    }
    else if (e.getActionCommand().equals("Visualize The Node")) {
      //send the node data to the visualizer
      if (m_focusNode >= 0) {
  Instances inst;
  if ((inst = m_nodes[m_focusNode].m_node.getInstances()) != null) {
    VisualizePanel pan = new VisualizePanel();
    pan.setInstances(inst);
    JFrame nf = new JFrame();
    nf.setSize(400, 300);
    nf.getContentPane().add(pan);
    nf.setVisible(true);
  }
  else {
    JOptionPane.showMessageDialog(this, "Sorry, there is no " +
          "available Instances data for " +
          "this Node.", "Sorry!",
          JOptionPane.WARNING_MESSAGE);
  }
      }
      else {
  JOptionPane.showMessageDialog(this, "Error, there is no " +
              "selected Node to perform " +
              "this operation on.", "Error!",
              JOptionPane.ERROR_MESSAGE);
      }
    }
    else if (e.getActionCommand().equals("Create Child Nodes")) {
      if (m_focusNode >= 0) {
  if (m_listener != null) {
    //then send message to the listener
    m_listener.userCommand(new TreeDisplayEvent
      (TreeDisplayEvent.ADD_CHILDREN,
       m_nodes[m_focusNode].m_node.getRefer()));
  }
  else {
    JOptionPane.showMessageDialog(this, "Sorry, there is no " +
          "available Decision Tree to " +
          "perform this operation on.",
          "Sorry!",
          JOptionPane.WARNING_MESSAGE);
  }
      }
      else {
  JOptionPane.showMessageDialog(this, "Error, there is no " +
              "selected Node to perform this " +
              "operation on.", "Error!",
              JOptionPane.ERROR_MESSAGE);
      }
    }
    else if (e.getActionCommand().equals("Remove Child Nodes")) {
      if (m_focusNode >= 0) {
  if (m_listener != null) {
    //then send message to the listener
    m_listener.userCommand(new
      TreeDisplayEvent(TreeDisplayEvent.REMOVE_CHILDREN,
           m_nodes[m_focusNode].m_node.getRefer()));
  }
  else {
    JOptionPane.showMessageDialog(this, "Sorry, there is no " +
          "available Decsion Tree to " +
          "perform this operation on.",
          "Sorry!",
          JOptionPane.WARNING_MESSAGE);
  }
      }
      else {
  JOptionPane.showMessageDialog(this, "Error, there is no " +
              "selected Node to perform this " +
              "operation on.", "Error!",
              JOptionPane.ERROR_MESSAGE);
      }
    }
    else if (e.getActionCommand().equals("classify_child")) {
      if (m_focusNode >= 0) {
  if (m_listener != null) {
    //then send message to the listener
    m_listener.userCommand(new TreeDisplayEvent
      (TreeDisplayEvent.CLASSIFY_CHILD,
       m_nodes[m_focusNode].m_node.getRefer()));
  }
  else {
    JOptionPane.showMessageDialog(this, "Sorry, there is no " +
          "available Decsion Tree to " +
          "perform this operation on.",
          "Sorry!",
          JOptionPane.WARNING_MESSAGE);
  }
      }
      else {
  JOptionPane.showMessageDialog(this, "Error, there is no " +
              "selected Node to perform this " +
              "operation on.", "Error!",
              JOptionPane.ERROR_MESSAGE);
      }
    }
    else if (e.getActionCommand().equals("send_instances")) {
      if (m_focusNode >= 0) {
  if (m_listener != null) {
    //then send message to the listener
    m_listener.userCommand(new TreeDisplayEvent
      (TreeDisplayEvent.SEND_INSTANCES,
       m_nodes[m_focusNode].m_node.getRefer()));
  }
  else {
    JOptionPane.showMessageDialog(this, "Sorry, there is no " +
          "available Decsion Tree to " +
          "perform this operation on.",
          "Sorry!",
          JOptionPane.WARNING_MESSAGE);
  }
      }
      else {
  JOptionPane.showMessageDialog(this, "Error, there is no " +
              "selected Node to perform this " +
              "operation on.", "Error!",
              JOptionPane.ERROR_MESSAGE);
      }
    }
    else if (e.getActionCommand().equals("Accept The Tree")) {
      if (m_listener != null) {
  //then send message to the listener saying that the tree is done
  m_listener.userCommand(new TreeDisplayEvent(TreeDisplayEvent.ACCEPT,
              null));
      }
      else {
  JOptionPane.showMessageDialog(this, "Sorry, there is no " +
              "available Decision Tree to " +
              "perform this operation on.",
              "Sorry!",
              JOptionPane.WARNING_MESSAGE);
      }
    }
  }

  /**
   * Performs the action associated with the ItemEvent.
   *
   * @param e the item event.
   */
  public void itemStateChanged(ItemEvent e)
  {
    JRadioButtonMenuItem c = (JRadioButtonMenuItem)e.getSource();
    if (c.getActionCommand().equals("Size 24")) {
      changeFontSize(24);
    }
    else if (c.getActionCommand().equals("Size 22")) {
      changeFontSize(22);
    }
    else if (c.getActionCommand().equals("Size 20")) {
      changeFontSize(20);
    }
    else if (c.getActionCommand().equals("Size 18")) {
      changeFontSize(18);
    }
    else if (c.getActionCommand().equals("Size 16")) {
      changeFontSize(16);
    }
    else if (c.getActionCommand().equals("Size 14")) {
      changeFontSize(14);
    }
    else if (c.getActionCommand().equals("Size 12")) {
      changeFontSize(12);
    }
    else if (c.getActionCommand().equals("Size 10")) {
      changeFontSize(10);
    }
    else if (c.getActionCommand().equals("Size 8")) {
      changeFontSize(8);
    }
    else if (c.getActionCommand().equals("Size 6")) {
      changeFontSize(6);
    }
    else if (c.getActionCommand().equals("Size 4")) {
      changeFontSize(4);
    }
    else if (c.getActionCommand().equals("Size 2")) {
      changeFontSize(2);
    }
    else if (c.getActionCommand().equals("Size 1")) {
      changeFontSize(1);
    }
    else if (c.getActionCommand().equals("Hide Descendants")) {
      //focus_node.setCVisible(!c.isSelected());
      //no longer used...
    }
  }

  /**
   * Does nothing.
   * @param e the mouse event.
   */
  public void mouseClicked(MouseEvent e) {
    //if the mouse was left clicked on
    //the node then
    if (m_clickAvailable) {
      //determine if the click was on a node or not
      int s = -1;
     
      for (int noa = 0; noa < m_numNodes;noa++) {
  if (m_nodes[noa].m_quad == 18) {
    //then is on the screen
    calcScreenCoords(noa);
    if (e.getX() <= m_nodes[noa].m_center + m_nodes[noa].m_side
        && e.getX()
        >= m_nodes[noa].m_center - m_nodes[noa].m_side &&
        e.getY() >= m_nodes[noa].m_top && e.getY()
        <= m_nodes[noa].m_top + m_nodes[noa].m_height) {
      //then it is this node that the mouse was clicked on
      s = noa;
    }
    m_nodes[noa].m_top = 32000;
  }
      }
      m_focusNode = s;
     
      if (m_focusNode != -1) {
  if (m_listener != null) {
    //then set this to be the selected node for editing
    actionPerformed(new ActionEvent(this, 32000, "Create Child Nodes"));
   
  }
  else {
    //then open a visualize to display this nodes instances if possible
    actionPerformed(new ActionEvent(this, 32000, "Visualize The Node"));
  }
      }
    }
  }
 
  /**
   * Determines what action the user wants to perform.
   *
   * @param e the mouse event.
   */ 
  public void mousePressed(MouseEvent e) {
    m_frameLimiter.setRepeats(true);
    if ((e.getModifiers() & e.BUTTON1_MASK) != 0 && !e.isAltDown() &&
  m_mouseState == 0
  && m_scaling == 0) {
      //then the left mouse button has been pressed
      //check for modifiers
     
      if (((e.getModifiers() & e.CTRL_MASK) != 0) && ((e.getModifiers() & e.SHIFT_MASK) == 0)) {
  //then is in zoom out mode
  m_mouseState = 2;
      }
      else if (((e.getModifiers() & e.SHIFT_MASK) != 0) && ((e.getModifiers() & e.CTRL_MASK) == 0)) {
  //then is in zoom mode
  //note if both are pressed default action is to zoom out
  m_oldMousePos.width = e.getX();
  m_oldMousePos.height = e.getY();
  m_newMousePos.width = e.getX();
  m_newMousePos.height = e.getY();
  m_mouseState = 3;
 
  Graphics g = getGraphics();
  g.setColor(Color.black);
  g.setXORMode(Color.white);
  g.drawRect(m_oldMousePos.width, m_oldMousePos.height,
       m_newMousePos.width - m_oldMousePos.width,
       m_newMousePos.height - m_oldMousePos.height);
  g.dispose();
      }
      else {
  //no modifiers drag area around
  m_oldMousePos.width = e.getX();
  m_oldMousePos.height = e.getY();
  m_newMousePos.width = e.getX();
  m_newMousePos.height = e.getY();
  m_mouseState = 1;
  m_frameLimiter.start();
      }
     
    }
    // pop up save dialog explicitly (is somehow overridden...)
    else if ( (e.getButton() == MouseEvent.BUTTON1) && e.isAltDown() && e.isShiftDown() && !e.isControlDown() ) {
      saveComponent();
    }
    else if (m_mouseState == 0 && m_scaling == 0) {
      //either middle or right mouse button pushed
      //determine menu to use
     
    }
  }
 
  /**
   * Performs the final stages of what the user wants to perform.
   *
   * @param e the mouse event.
   */
  public void mouseReleased(MouseEvent e) {
    if (m_mouseState == 1) {
      //this is used by mouseClicked to determine if it is alright to do
      //something
      m_clickAvailable = true;
  //note that a standard click with the left mouse is pretty much the
      //only safe input left to be assigned anything.
    }
    else {
      m_clickAvailable = false;
    }
    if (m_mouseState == 2 && mouseInBounds(e)) {
      //then zoom out;
      m_mouseState = 0;
      Dimension ns = new Dimension(m_viewSize.width / 2, m_viewSize.height
           / 2);
      if (ns.width < 10) {
  ns.width = 10;
      }
      if (ns.height < 10) {
  ns.height = 10;
      }
     
      Dimension d = getSize();
      Dimension np = new Dimension((int)(d.width / 2
           - ((double)d.width / 2
              - m_viewPos.width) / 2),
           (int)(d.height / 2
           - ((double)d.height / 2
              - m_viewPos.height) / 2));
     
      animateScaling(np, ns, 10);
     
      //view_pos.width += view_size.width / 2;
      //view_pos.height += view_size.height / 2;
     
    }
    else if (m_mouseState == 3) {
      //then zoom in
      m_mouseState = 0;
      Graphics g = getGraphics();
      g.setColor(Color.black);
      g.setXORMode(Color.white);
      g.drawRect(m_oldMousePos.width, m_oldMousePos.height,
     m_newMousePos.width - m_oldMousePos.width,
     m_newMousePos.height - m_oldMousePos.height);
      g.dispose();
     
     
     
      int cw = m_newMousePos.width - m_oldMousePos.width;
      int ch = m_newMousePos.height - m_oldMousePos.height;
      if (cw >= 1 && ch >= 1) {
  if (mouseInBounds(e) &&
      (getSize().width / cw) <= 6 &&
      (getSize().height / ch) <= 6) {
   
    //now calculate new position and size
    Dimension ns = new Dimension();
    Dimension np = new Dimension();
    double nvsw = getSize().width / (double)(cw);
    double nvsh = getSize().height / (double)(ch);
    np.width = (int)((m_oldMousePos.width - m_viewPos.width) * -nvsw);
    np.height = (int)((m_oldMousePos.height - m_viewPos.height) * -nvsh);
    ns.width = (int)(m_viewSize.width * nvsw);
    ns.height = (int)(m_viewSize.height * nvsh);
   
    animateScaling(np, ns, 10);
   
   
  }
      }
    }
    else if (m_mouseState == 0 && m_scaling == 0) {
      //menu
      m_mouseState = 0;
      setFont(new Font("A Name", 0, 12));
      //determine if the click was on a node or not
      int s = -1;
     
      for (int noa = 0; noa < m_numNodes;noa++) {
  if (m_nodes[noa].m_quad == 18) {
    //then is on the screen
    calcScreenCoords(noa);
    if (e.getX() <= m_nodes[noa].m_center + m_nodes[noa].m_side
        && e.getX()
        >= m_nodes[noa].m_center - m_nodes[noa].m_side &&
        e.getY() >= m_nodes[noa].m_top && e.getY()
        <= m_nodes[noa].m_top + m_nodes[noa].m_height) {
      //then it is this node that the mouse was clicked on
      s = noa;
    }
    m_nodes[noa].m_top = 32000;
  }
      }
      if (s == -1) {
  //the mouse wasn't clicked on a node
  m_winMenu.show(this,e.getX(),e.getY());
      }
      else {
  //the mouse was clicked on a node
  m_focusNode = s;
  m_nodeMenu.show(this, e.getX(), e.getY());
 
      }
      setFont(m_currentFont);
    }
    else if (m_mouseState == 1) {
      //dragging
      m_mouseState = 0;
      m_frameLimiter.stop();
      repaint();
    }
   
  }
 
  /**
   * Checks to see if the coordinates of the mouse lie on this JPanel.
   *
   * @param e the mouse event.
   * @return true if the mouse lies on this JPanel.
   */
  private boolean mouseInBounds(MouseEvent e) {
    //this returns true if the mouse is currently over the canvas otherwise
    //false
   
    if (e.getX() < 0 || e.getY() < 0 || e.getX() > getSize().width
  || e.getY() > getSize().height) {
      return false;
    }
    return true;
  }
 
  /**
   * Performs intermediate updates to what the user wishes to do.
   *
   * @param e the mouse event.
   */
  public void mouseDragged(MouseEvent e) {
    //use mouse state to determine what to do to the view of the tree
   
    if (m_mouseState == 1) {
      //then dragging view
      m_oldMousePos.width = m_newMousePos.width;
      m_oldMousePos.height = m_newMousePos.height;
      m_newMousePos.width = e.getX();
      m_newMousePos.height = e.getY();
      m_viewPos.width += m_newMousePos.width - m_oldMousePos.width;
      m_viewPos.height += m_newMousePos.height - m_oldMousePos.height;
     
     
    }
    else if (m_mouseState == 3) {
      //then zoom box being created
      //redraw the zoom box
      Graphics g = getGraphics();
      g.setColor(Color.black);
      g.setXORMode(Color.white);
      g.drawRect(m_oldMousePos.width, m_oldMousePos.height,
     m_newMousePos.width - m_oldMousePos.width,
     m_newMousePos.height - m_oldMousePos.height);
     
      m_newMousePos.width = e.getX();
      m_newMousePos.height = e.getY();
     
      g.drawRect(m_oldMousePos.width, m_oldMousePos.height,
     m_newMousePos.width - m_oldMousePos.width,
     m_newMousePos.height - m_oldMousePos.height);
      g.dispose();
    }
   
   
  }
 
  /**
   * Does nothing.
   *
   * @param e the mouse event.
   */
  public void mouseMoved(MouseEvent e) {
  }

  /**
   * Does nothing.
   *
   * @param e the mouse event.
   */
  public void mouseEntered(MouseEvent e) {
  }
 
  /**
   * Does nothing.
   *
   * @param e the mouse event.
   */
  public void mouseExited(MouseEvent e) {
  }

  /**
   * Set the highlight for the node with the given id
   * @param id the id of the node to set the highlight for
   */
  public void setHighlight(String id) {
    //set the highlight for the node with the given id
   
    for (int noa = 0; noa < m_numNodes; noa++) {
      if (id.equals(m_nodes[noa].m_node.getRefer())) {
  //then highlight this node
  m_highlightNode = noa;
      }
    }
    //System.out.println("ahuh " + highlight_node + " " +
    //nodes[0].node.getRefer());
    repaint();
   
  }
 
  /**
   * Updates the screen contents.
   *
   * @param g the drawing surface.
   */
  public void paintComponent(Graphics g) {
    //System.out.println(nodes[0].top + "this is seriously pissing me off");
    g.clearRect(0, 0, getSize().width, getSize().height);
    g.setClip(3, 7, getWidth() - 6, getHeight() - 10);
    painter(g);
    g.setClip(0, 0, getWidth(), getHeight());
   
  }

  /**
   * Draws the tree to the graphics context
   *
   * @param g the drawing surface.
   */
  private void painter(Graphics g) {
    //I have moved what would normally be in the paintComponent
    //function to here
    //for now so that if I do in fact need to do double
    //buffering or the like it will be easier

    //this will go through the table of edges and draw the edge if it deems the
    //two nodes attached to it could cause it to cut the screen or be on it.
   
    //in the process flagging all nodes so that they can quickly be put to the
    //screen if they lie on it

    //I do it in this order because in some circumstances I have seen a line
    //cut through a node , to make things look better the line will
    //be drawn under the node


    //converting the screen edges to the node scale so that they
    //can be positioned relative to the screen
    //note I give a buffer around the edges of the screen.
   
    //when seeing
    //if a node is on screen I only bother to check the nodes top centre
    //if it has large enough size it may still fall onto the screen
    double left_clip = (double)(-m_viewPos.width - 50) / m_viewSize.width;
    double right_clip = (double)(getSize().width - m_viewPos.width + 50) /
      m_viewSize.width;
    double top_clip = (double)(-m_viewPos.height - 50) / m_viewSize.height;
    double bottom_clip = (double)(getSize().height - m_viewPos.height + 50) /
      m_viewSize.height;
 

   
    //  12 10  9           //the quadrants
    //  20 18 17
    //  36 34 33


    //first the edges must be rendered

    Edge e;
    Node r,s;
    double ncent,ntop;   

    int row = 0, col = 0, pq, cq;
    for (int noa = 0 ; noa < m_numNodes ; noa++) {
      r = m_nodes[noa].m_node;
      if (m_nodes[noa].m_change) {
  //then recalc row component of quadrant
  ntop = r.getTop();
  if (ntop < top_clip) {
    row = 8;
  }
  else if (ntop > bottom_clip) {
    row = 32;
  }
  else {
    row = 16;
  }
      }
     
      //calc the column the node falls in for the quadrant
      ncent = r.getCenter();
      if (ncent < left_clip) {
  col = 4;
      }
      else if (ncent > right_clip) {
  col = 1;
      }
      else {
  col = 2;
      }
     
      m_nodes[noa].m_quad = row | col;
     
      if (m_nodes[noa].m_parent >= 0) {
  //this will draw the edge if it should be drawn
  //It will do this by eliminating all edges that definitely won't enter
  //the screen and then draw the rest
 
  pq = m_nodes[m_edges[m_nodes[noa].m_parent].m_parent].m_quad;
  cq = m_nodes[noa].m_quad;
 
  //note that this will need to be altered if more than 1 parent exists
  if ((cq & 8) == 8) {
    //then child exists above screen
  }
  else if ((pq & 32) == 32) {
    //then parent exists below screen
  }
  else if ((cq & 4) == 4 && (pq & 4) == 4) {
    //then both child and parent exist to the left of the screen
  }
  else if ((cq & 1) == 1 && (pq & 1) == 1) {
    //then both child and parent exist to the right of the screen
  }
  else {
    //then draw the line
    drawLine(m_nodes[noa].m_parent, g);
  }
      }
     
      //now draw the nodes
    }
   
    for (int noa = 0 ;noa < m_numNodes; noa++) {
      if (m_nodes[noa].m_quad == 18) {
  //then the node is on the screen , draw it
  drawNode(noa, g);
      }
    }
   
    if (m_highlightNode >= 0 && m_highlightNode < m_numNodes) {
      //then draw outline
      if (m_nodes[m_highlightNode].m_quad == 18) {
  Color acol = m_nodes[m_highlightNode].m_node.getColor();
  g.setColor(new Color((acol.getRed() + 125) % 256,
           (acol.getGreen() + 125) % 256,
           (acol.getBlue() + 125) % 256));
  //g.setXORMode(Color.white);
  if (m_nodes[m_highlightNode].m_node.getShape() == 1) {
    g.drawRect(m_nodes[m_highlightNode].m_center
         - m_nodes[m_highlightNode].m_side,
         m_nodes[m_highlightNode].m_top,
         m_nodes[m_highlightNode].m_width,
         m_nodes[m_highlightNode].m_height);
   
    g.drawRect(m_nodes[m_highlightNode].m_center
         - m_nodes[m_highlightNode].m_side
         + 1, m_nodes[m_highlightNode].m_top + 1,
         m_nodes[m_highlightNode].m_width - 2,
         m_nodes[m_highlightNode].m_height - 2);
  }
  else if (m_nodes[m_highlightNode].m_node.getShape() == 2) {
    g.drawOval(m_nodes[m_highlightNode].m_center
         - m_nodes[m_highlightNode].m_side,
         m_nodes[m_highlightNode].m_top,
         m_nodes[m_highlightNode].m_width,
         m_nodes[m_highlightNode].m_height);
   
    g.drawOval(m_nodes[m_highlightNode].m_center
         - m_nodes[m_highlightNode].m_side
         + 1, m_nodes[m_highlightNode].m_top + 1,
         m_nodes[m_highlightNode].m_width - 2,
         m_nodes[m_highlightNode].m_height - 2);
  }
      }
    }
   
    for (int noa = 0;noa < m_numNodes;noa++) {
      //this resets the coords so that next time a refresh occurs
      //they don't accidentally get used
      //I will use 32000 to signify that they are invalid, even if this
      //coordinate occurs it doesn't
      //matter as it is only for the sake of the caching
     
      m_nodes[noa].m_top = 32000;
    }
  }
 
  /**
   * Determines the attributes of the node and draws it.
   *
   * @param n A subscript identifying the node in <i>nodes</i> array
   * @param g The drawing surface
   */
  private void drawNode(int n, Graphics g) {
    //this will draw a node and then print text on it
   
    g.setColor(m_nodes[n].m_node.getColor());
    g.setPaintMode();
    calcScreenCoords(n);
    int x = m_nodes[n].m_center - m_nodes[n].m_side;
    int y = m_nodes[n].m_top;
    if (m_nodes[n].m_node.getShape() == 1) {
      g.fill3DRect(x, y, m_nodes[n].m_width, m_nodes[n].m_height, true);
      drawText(x, y, n, false, g);
     
    }
    else if (m_nodes[n].m_node.getShape() == 2) {
     
      g.fillOval(x, y, m_nodes[n].m_width, m_nodes[n].m_height);
      drawText(x, y + (int)(m_nodes[n].m_height * .15), n, false, g);
    }
  }
 
  /**
   * Determines the attributes of the edge and draws it.
   *
   * @param e A subscript identifying the edge in <i>edges</i> array.
   * @param g The drawing surface.
   */
  private void drawLine(int e, Graphics g) {
    //this will draw a line taking in the edge number and then getting
    //the nodes subscript for the parent and child entries
   
    //this will draw a line that has been broken in the middle
    //for the edge text to be displayed
    //if applicable
   
    //first convert both parent and child node coords to screen coords
    int p = m_edges[e].m_parent;
    int c = m_edges[e].m_child;
    calcScreenCoords(c);
    calcScreenCoords(p);
   
    g.setColor(Color.black);
    g.setPaintMode();
   
    if (m_currentFont.getSize() < 2) {
      //text to small to bother cutting the edge
      g.drawLine(m_nodes[p].m_center, m_nodes[p].m_top + m_nodes[p].m_height,
     m_nodes[c].m_center, m_nodes[c].m_top);
     
    }
    else {
      //find where to cut the edge to insert text
      int e_width = m_nodes[c].m_center - m_nodes[p].m_center;
      int e_height = m_nodes[c].m_top - (m_nodes[p].m_top
           + m_nodes[p].m_height);
      int e_width2 = e_width / 2;
      int e_height2 = e_height / 2;
      int e_centerx = m_nodes[p].m_center + e_width2;
      int e_centery = m_nodes[p].m_top + m_nodes[p].m_height + e_height2;
      int e_offset = m_edges[e].m_tb;
     
      int tmp = (int)(((double)e_width / e_height) *
          (e_height2 - e_offset)) + m_nodes[p].m_center;
      //System.out.println(edges[e].m_height);
     
      //draw text now
     
      drawText(e_centerx - m_edges[e].m_side, e_centery - e_offset, e, true
         , g);
     
     
      if (tmp > (e_centerx - m_edges[e].m_side) && tmp
    < (e_centerx + m_edges[e].m_side)) {
  //then cut line on top and bottom of text
  g.drawLine(m_nodes[p].m_center, m_nodes[p].m_top + m_nodes[p].m_height
       , tmp, e_centery - e_offset)//first segment
  g.drawLine(e_centerx * 2 - tmp, e_centery + e_offset,
       m_nodes[c].m_center, m_nodes[c].m_top);    //second segment
      }
      else {
  e_offset = m_edges[e].m_side;
  if (e_width < 0) {
    e_offset *= -1;   //adjusting for direction which could otherwise
    //screw up the calculation
  }
  tmp = (int)(((double)e_height / e_width) * (e_width2 - e_offset)) +
    m_nodes[p].m_top + m_nodes[p].m_height;
 
  g.drawLine(m_nodes[p].m_center, m_nodes[p].m_top + m_nodes[p].m_height
       , e_centerx - e_offset, tmp);   //first segment
  g.drawLine(e_centerx + e_offset, e_centery * 2 - tmp,
       m_nodes[c].m_center, m_nodes[c].m_top)//second segment
 
      }
    }
    //System.out.println("here" + nodes[p].center);
  }

  /**
   * Draws the text for either an Edge or a Node.
   *
   * @param x1 the left side of the text area.
   * @param y1 the top of the text area.
   * @param s A subscript identifying either a Node or Edge.
   * @param e_or_n Distinguishes whether it is a node or edge.
   * @param g The drawing surface.
   */
  private void drawText(int x1, int y1, int s, boolean e_or_n, Graphics g) {
    //this function will take in the rectangle that the text should be
    //drawn in as well as the subscript
    //for either the edge or node and a boolean variable to tell which

    g.setPaintMode();
    g.setColor(Color.black);
    String st;
    if (e_or_n) {
      //then paint for edge
      Edge e = m_edges[s].m_edge;
      for (int noa = 0;(st = e.getLine(noa)) != null; noa++) {
  g.drawString(st, (m_edges[s].m_width - m_fontSize.stringWidth(st)) / 2
         + x1,
         y1 + (noa + 1) * m_fontSize.getHeight());
      }
    }
    else {
      //then paint for node
      Node e = m_nodes[s].m_node;
      for (int noa = 0;(st = e.getLine(noa)) != null; noa++) {
  g.drawString(st, (m_nodes[s].m_width - m_fontSize.stringWidth(st)) / 2
         + x1,
         y1 + (noa + 1) * m_fontSize.getHeight());
      }
    }
  }
 
  /**
   * Converts the internal coordinates of the node found from <i>n</i>
   * and converts them to the actual screen coordinates.
   *
   * @param n A subscript identifying the Node.
   */
  private void calcScreenCoords(int n) {
    //this converts the coordinate system the Node uses into screen coordinates
    // System.out.println(n + " " + view_pos.height + " " +
    //nodes[n].node.getCenter());
    if (m_nodes[n].m_top == 32000) {
      m_nodes[n].m_top = ((int)(m_nodes[n].m_node.getTop()
        * m_viewSize.height)) + m_viewPos.height;
      m_nodes[n].m_center = ((int)(m_nodes[n].m_node.getCenter()
           * m_viewSize.width)) + m_viewPos.width;
    }
  }

  /**
   * This Calculates the minimum size of the tree which will prevent any text
   * overlapping and make it readable, and then set the size of the tree to
   * this.
   */
  private void autoScale() {
    //this function will determine the smallest scale value that keeps the text
    //from overlapping
    //it will leave the view centered
   
    int dist;
    Node ln,rn;
    Dimension temp = new Dimension(10, 10);
   
    if (m_numNodes <= 1) {
      return;
    }
   
    //calc height needed by first node
    dist = (m_nodes[0].m_height + 40) * m_numLevels;
    if (dist > temp.height) {
      temp.height = dist;
    }
   
    for (int noa = 0;noa < m_numNodes - 1;noa++) {
      calcScreenCoords(noa)
      calcScreenCoords(noa+1);
      if (m_nodes[noa+1].m_change) {
  //then on a new level so don't check width this time round
      }
      else {
 
  dist = m_nodes[noa+1].m_center - m_nodes[noa].m_center;
  //the distance between the node centers, along horiz
  if (dist <= 0) {
    dist = 1;
  }
  dist = ((6 + m_nodes[noa].m_side + m_nodes[noa+1].m_side)
    * m_viewSize.width) / dist; //calc optimal size for width
 
  if (dist > temp.width) {
   
    temp.width = dist;
  }
      }
      //now calc.. minimun hieght needed by nodes
     
      dist = (m_nodes[noa+1].m_height + 40) * m_numLevels;
      if (dist > temp.height) {
 
  temp.height = dist;
      }
    }
   
    int y1, y2, xa, xb;
   
    y1 = m_nodes[m_edges[0].m_parent].m_top;
    y2 = m_nodes[m_edges[0].m_child].m_top;
   
    dist = y2 - y1;
    if (dist <= 0) {
      dist = 1;
    }
    dist = ((60 + m_edges[0].m_height + m_nodes[m_edges[0].m_parent].m_height)
      * m_viewSize.height) / dist;
    if (dist > temp.height) {
     
      temp.height = dist;
    }
   
    for (int noa = 0;noa < m_numNodes - 2; noa++) {
      //check the edges now
      if (m_nodes[m_edges[noa+1].m_child].m_change) {
  //then edge is on a different level , so skip this one
      }
      else {
  //calc the width requirements of this pair of edges
 
  xa = m_nodes[m_edges[noa].m_child].m_center
    - m_nodes[m_edges[noa].m_parent].m_center;
  xa /= 2;
  xa += m_nodes[m_edges[noa].m_parent].m_center;
 
  xb = m_nodes[m_edges[noa+1].m_child].m_center -
    m_nodes[m_edges[noa+1].m_parent].m_center;
  xb /= 2;
  xb += m_nodes[m_edges[noa+1].m_parent].m_center;
 
  dist = xb - xa;
  if (dist <= 0) {
    dist = 1;
  }
  dist = ((12 + m_edges[noa].m_side + m_edges[noa+1].m_side)
    * m_viewSize.width)
    / dist;
  if (dist > temp.width) {
   
    temp.width = dist;
  }
      }
      //now calc height need by the edges
      y1 = m_nodes[m_edges[noa+1].m_parent].m_top;
      y2 = m_nodes[m_edges[noa+1].m_child].m_top;
     
      dist = y2 - y1;
      if (dist <= 0) {
 
  dist = 1;
      }
      dist = ((60 + m_edges[noa+1].m_height
         + m_nodes[m_edges[noa+1].m_parent].m_height)
        * m_viewSize.height) / dist;
     
      if (dist > temp.height) {
 
  temp.height = dist;
      }
    }

    Dimension e = getSize();
   
    Dimension np = new Dimension();
    np.width = (int)(e.width / 2 (((double)e.width / 2) - m_viewPos.width) /
         ((double)m_viewSize.width) * (double)temp.width);
    np.height = (int)(e.height / 2 (((double)e.height / 2) -
               m_viewPos.height) /      
          ((double)m_viewSize.height) * (double)temp.height);
    //animate_scaling(c_size,c_pos,25);
   
    for (int noa = 0;noa < m_numNodes;noa++) {
      //this resets the coords so that next time a refresh occurs they don't
      //accidentally get used
      //I will use 32000 to signify that they are invalid, even if this
      //coordinate occurs it doesn't
      //matter as it is only for the sake of the caching
     
      m_nodes[noa].m_top = 32000;
     
    }
    animateScaling(np, temp, 10);
  }

  /**
   * This will increment the size and position of the tree towards the
   * desired size and position
   * a little (depending on the value of <i>frames</i>) everytime it is called.
   *
   * @param n_pos The final position of the tree wanted.
   * @param n_size The final size of the tree wanted.
   * @param frames The number of frames that shall occur before the final
   * size and pos is reached.
   */
  private void animateScaling(Dimension n_pos,Dimension n_size,int frames) {
    //this function will take new size and position coords , and incrementally
    //scale the view to these
    //since I will be tying it in with the framelimiter I will simply call
    //this function and increment it once
    //I will have to use a global variable since I am doing it proportionally

    if (frames == 0) {
      System.out.println("the timer didn't end in time");
      m_scaling = 0;
    }
    else {
      if (m_scaling == 0) {
  //new animate session
  //start timer and set scaling
  m_frameLimiter.start();
  m_nViewPos.width = n_pos.width;
  m_nViewPos.height = n_pos.height;
  m_nViewSize.width = n_size.width;
  m_nViewSize.height = n_size.height;
 
  m_scaling = frames;
      }
     
      int s_w = (n_size.width - m_viewSize.width) / frames;
      int s_h = (n_size.height - m_viewSize.height) / frames;
      int p_w = (n_pos.width - m_viewPos.width) / frames;
      int p_h = (n_pos.height - m_viewPos.height) / frames;
     
      m_viewSize.width += s_w;
      m_viewSize.height += s_h;
     
      m_viewPos.width += p_w;
      m_viewPos.height += p_h;
     
      repaint();
     
      m_scaling--;
      if (m_scaling == 0) {
  //all done
  m_frameLimiter.stop();
      }
    }
  }
 
  /**
   * This will change the font size for displaying the tree to the one
   * specified.
   *
   * @param s The new pointsize of the font.
   */
  private void changeFontSize(int s) {
    //this will set up the new font that shall be used
    //it will also recalculate the size of the nodes as these will change as
    //a result of
    //the new font size
    setFont(m_currentFont = new Font("A Name", 0, s));            

    m_fontSize = getFontMetrics(getFont());
   
    Dimension d;

    for (int noa = 0; noa < m_numNodes; noa++) {
      //this will set the size info for each node and edge
     
      d = m_nodes[noa].m_node.stringSize(m_fontSize);
     
      if (m_nodes[noa].m_node.getShape() == 1) {
  m_nodes[noa].m_height = d.height + 10;
  m_nodes[noa].m_width = d.width + 8;
  m_nodes[noa].m_side = m_nodes[noa].m_width / 2;
      }
      else if (m_nodes[noa].m_node.getShape() == 2) {
  m_nodes[noa].m_height = (int)((d.height + 2) * 1.6);
  m_nodes[noa].m_width = (int)((d.width + 2) * 1.6);
  m_nodes[noa].m_side = m_nodes[noa].m_width / 2;
      }
     
      if (noa < m_numNodes - 1) {
  //this will do the same for edges
 
  d = m_edges[noa].m_edge.stringSize(m_fontSize);
 
  m_edges[noa].m_height =  d.height + 8;
  m_edges[noa].m_width = d.width + 8;
  m_edges[noa].m_side = m_edges[noa].m_width / 2;
  m_edges[noa].m_tb = m_edges[noa].m_height / 2;
      }
    }
  }

  /**
   * This will fill two arrays with the Nodes and Edges from the tree
   * into a particular order.
   *
   * @param t The top Node of the tree.
   * @param l An array that has already been allocated, to be filled.
   * @param k An array that has already been allocated, to be filled.
   */
  private void arrayFill(Node t, NodeInfo[] l, EdgeInfo[] k) {
   
    //this will take the top node and the array to fill
    //it will go through the tree structure and and fill the array with the
    //nodes
    //from top to bottom left to right
   
    //note I do not believe this function will be able to deal with multiple
    //parents

    if (t == null || l == null) {
      System.exit(1);      //this is just a preliminary safety check
      //(i shouldn' need it)
    }
   
    Edge e;
    Node r,s;
    l[0] = new NodeInfo();
    l[0].m_node = t;
    l[0].m_parent = -1;
    l[0].m_change = true;
   
    int floater;       //this will point at a node that has previously been
    //put in the list
    //all of the children that this node has shall be put in the list ,
    //once this is done the floater shall point at the next node in the list
    //this will allow the nodes to be put into order from closest to top node
    //to furtherest from top node

    int free_space = 1; //the next empty array position

    double height = t.getTop(); //this will be used to determine if the node
    //has a
    //new height compared to the
    //previous one

    for (floater = 0;floater < free_space;floater++) {
      r = l[floater].m_node;
      for (int noa = 0;(e = r.getChild(noa)) != null;noa++) {
  //this loop pulls out each child of r
 
  //e points to a child edge, getTarget will return that edges child node
  s = e.getTarget();
  l[free_space] = new NodeInfo();
  l[free_space].m_node = s;
  l[free_space].m_parent = free_space - 1;
 
  k[free_space - 1] = new EdgeInfo();
  k[free_space - 1].m_edge = e;
  k[free_space - 1].m_parent = floater;
  k[free_space - 1].m_child = free_space;     //note although it's child
  //will always have a subscript
  //of 1 more , I may not nessecarily have access to that
  //and it will need the subscr.. for multiple parents
 
  //determine if level of node has changed from previous one
  if (height != s.getTop()) {
    l[free_space].m_change = true;
    height = s.getTop();
  }
  else {
    l[free_space].m_change = false;
  }
  free_space++;
      }
    }
  }

  /**
   * Internal Class for containing display information about a Node.
   */
  private class NodeInfo {
    //this class contains a pointer to the node itself along with extra
    //information
    //about the node used by the Displayer class

    /** The y pos of the node on screen. */
    int m_top = 32000;           //the main node coords calculated out

    /** The x pos of the node on screen. */
    int m_center;        // these coords will probably change each refresh

    //and are the positioning coords
    //which the rest of the offsets use
   
    /** The offset to get to the left or right of the node. */
    int m_side;          //these are the screen offset for the dimensions of

    //the node relative to the nodes
    //internal top and center values (after they have been converted to
    //screen coords
    /** The width of the node. */
    int m_width;

    /** The height of the node. */
    int m_height;

    /** True if the node is at the start (left) of a new level (not sibling
     * group). */
    boolean m_change;    //this is quickly used to identify whether the node
    //has chenged height from the
    //previous one to help speed up the calculation of what row it lies in

    /** The subscript number of the Nodes parent. */
    int m_parent;     //this is the index of the nodes parent edge in an array

    /** The rough position of the node relative to the screen. */
    int m_quad;       //what of nine quadrants is it in

    /*
      12 10  9
      20 18 17          //18 being the screen
      36 34 33          //this arrangement uses 6 bits, each bit represents a
      row or column
    */

    /** The Node itself. */
    Node m_node;
  }

  /**
   * Internal Class for containing display information about an Edge.
   */
  private class EdgeInfo {
    //this class contains a pointer to the edge along with all the other
    //extra info about the edge
   
    /** The parent subscript (for a Node). */
    int m_parent;            //array indexs for its two connections

    /** The child subscript (for a Node). */
    int m_child;
   

    /** The distance from the center of the text to either side. */
    int m_side;            //these are used to describe the dimensions of the
    //text

    /** The distance from the center of the text to top or bottom. */
    int m_tb;              //tb stands for top , bottom, this is simply the
    //distance from the middle to top bottom

    /** The width of the text. */
    int m_width;

    /** The height of the text. */
    int m_height;

    /** The Edge itself. */
    Edge m_edge;
  }

  /**
   * Main method for testing this class.
   * @param args first argument should be the name of a file that contains
   * a tree discription in dot format.
   */
  public static void main(String[] args)
  {
    try
      {
  //put in the random data generator right here
  // this call with import java.lang gives me between 0 and 1 Math.random
  TreeBuild builder = new TreeBuild();
  Node top = null;
  NodePlace arrange = new PlaceNode2();
  //top = builder.create(new StringReader("digraph atree { top [label=\"the top\"] a [label=\"the first node\"] b [label=\"the second nodes\"] c [label=\"comes off of first\"] top->a top->b b->c }"));
  top = builder.create(new FileReader(args[0]));
   
  int num = top.getCount(top,0);
  //System.out.println("counter counted " + num + " nodes");
  //System.out.println("there are " + num + " nodes");
  TreeVisualizer a = new TreeVisualizer(null, top, arrange);
  a.setSize(800 ,600);
  //a.setTree(top);
  JFrame f;
  f = new JFrame();
  //a.addMouseMotionListener(a);
  //a.addMouseListener(a);
  //f.add(a);
        Container contentPane = f.getContentPane();
  contentPane.add(a);
   f.setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE);
  f.setSize(800,600);
  f.setVisible(true);
  //f.
  //find_prop(top);
  //a.setTree(top,arrange);//,(num + 1000), num / 2 + 1000);
      }
    catch(IOException e){}
  }
}











TOP

Related Classes of weka.gui.treevisualizer.TreeVisualizer

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.