weka.gui.treevisualizer.TreeVisualizer.java Source code

Java tutorial

Introduction

Here is the source code for weka.gui.treevisualizer.TreeVisualizer.java

Source

/*
 *   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 3 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, see <http://www.gnu.org/licenses/>.
 */

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

package weka.gui.treevisualizer;

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.Graphics2D;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.InputEvent;
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 java.util.Properties;

import javax.swing.*;

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

/**
 * 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$
 */
public class TreeVisualizer extends PrintablePanel
        implements MouseMotionListener, MouseListener, ActionListener, ItemListener {

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

    /** the props file. */
    public final static String PROPERTIES_FILE = "weka/gui/treevisualizer/TreeVisualizer.props";

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

    /** The top Node. */
    private final Node m_topNode;

    /** The postion of the view relative to the tree. */
    private final Dimension m_viewPos;

    /** The size of the tree in pixels. */
    private final 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 final int m_numNodes;

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

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

    /**
     * An array with the Edges sorted into it and display information about the
     * Edges.
     */
    private final EdgeInfo[] m_edges;

    /** A timer to keep the frame rate constant. */
    private final 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 final Dimension m_oldMousePos;

    /** A variable used to tag the most current point of a user action. */
    private final 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 final Dimension m_nViewPos;

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

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

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

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

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

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

    /** A sub group on the win_menu */
    private final JMenu m_selectFont;

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

    /** A visualize choice for the node, may not be available. */
    private final 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; NOT USED

    /** 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 final TreeDisplayListener m_listener;

    // private JTextField m_searchString; NOT USED
    // private JDialog m_searchWin; NOT USED
    // private JRadioButton m_caseSen; NOT USED

    /** the font color. */
    protected Color m_FontColor = null;

    /** the background color. */
    protected Color m_BackgroundColor = null;

    /** the node color. */
    protected Color m_NodeColor = null;

    /** the line color. */
    protected Color m_LineColor = null;

    /** the color of the zoombox. */
    protected Color m_ZoomBoxColor = null;

    /** the XOR color of the zoombox. */
    protected Color m_ZoomBoxXORColor = null;

    /** whether to show the border or not. */
    protected boolean m_ShowBorder = true;

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

    // 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();

        initialize();

        // generate the node structure in here
        if (m_ShowBorder) {
            setBorder(BorderFactory.createTitledBorder("Tree View"));
        }
        m_listener = tdl;

        TreeBuild builder = new TreeBuild();

        Node n = null;
        // NodePlace arrange = new PlaceNode2(); NOT USED
        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 = Node.getCount(m_topNode, 0); // note the second
        // argument must be a zero, this is a
        // recursive function

        m_numLevels = Node.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);

        if (m_listener != null) {
            m_winMenu.addSeparator();
            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();

        initialize();

        // if the size needs to be automatically alocated I will do it here
        if (m_ShowBorder) {
            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 = Node.getCount(m_topNode, 0); // note the second
        // argument must be a zero, this is a
        // recursive function

        m_numLevels = Node.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();
    }

    /**
     * Processes the color string. Returns null if empty.
     * 
     * @param colorStr the string to process
     * @return the processed color or null
     */
    protected Color getColor(String colorStr) {
        Color result;

        result = null;

        if ((colorStr != null) && (colorStr.length() > 0)) {
            result = VisualizeUtils.processColour(colorStr, result);
        }

        return result;
    }

    /**
     * Performs some initialization.
     */
    protected void initialize() {
        Properties props;

        try {
            props = Utils.readProperties(PROPERTIES_FILE);
        } catch (Exception e) {
            e.printStackTrace();
            props = new Properties();
        }

        m_FontColor = getColor(props.getProperty("FontColor", ""));
        m_BackgroundColor = getColor(props.getProperty("BackgroundColor", ""));
        m_NodeColor = getColor(props.getProperty("NodeColor", ""));
        m_LineColor = getColor(props.getProperty("LineColor", ""));
        m_ZoomBoxColor = getColor(props.getProperty("ZoomBoxColor", ""));
        m_ZoomBoxXORColor = getColor(props.getProperty("ZoomBoxXORColor", ""));
        m_ShowBorder = Boolean.parseBoolean(props.getProperty("ShowBorder", "true"));
    }

    /**
     * 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.
     */
    @Override
    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 = Utils.getWekaJFrame("", this);
                    nf.getContentPane().add(pan);
                    nf.addWindowListener(new java.awt.event.WindowAdapter() {
                        @Override
                        public void windowClosing(java.awt.event.WindowEvent e) {
                            nf.dispose();
                        }
                    });
                    nf.pack();
                    nf.setSize(800, 600);
                    nf.setLocationRelativeTo(SwingUtilities.getWindowAncestor(this));
                    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.
     */
    @Override
    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.
     */
    @Override
    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.
     */
    @Override
    public void mousePressed(MouseEvent e) {
        m_frameLimiter.setRepeats(true);
        if ((e.getModifiers() & InputEvent.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() & InputEvent.CTRL_MASK) != 0)
                    && ((e.getModifiers() & InputEvent.SHIFT_MASK) == 0)) {
                // then is in zoom out mode
                m_mouseState = 2;
            } else if (((e.getModifiers() & InputEvent.SHIFT_MASK) != 0)
                    && ((e.getModifiers() & InputEvent.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();
                if (m_ZoomBoxColor == null) {
                    g.setColor(Color.black);
                } else {
                    g.setColor(m_ZoomBoxColor);
                }
                if (m_ZoomBoxXORColor == null) {
                    g.setXORMode(Color.white);
                } else {
                    g.setXORMode(m_ZoomBoxXORColor);
                }
                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.
     */
    @Override
    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();
            if (m_ZoomBoxColor == null) {
                g.setColor(Color.black);
            } else {
                g.setColor(m_ZoomBoxColor);
            }
            if (m_ZoomBoxXORColor == null) {
                g.setXORMode(Color.white);
            } else {
                g.setXORMode(m_ZoomBoxXORColor);
            }
            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.
     */
    @Override
    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();
            if (m_ZoomBoxColor == null) {
                g.setColor(Color.black);
            } else {
                g.setColor(m_ZoomBoxColor);
            }
            if (m_ZoomBoxXORColor == null) {
                g.setXORMode(Color.white);
            } else {
                g.setXORMode(m_ZoomBoxXORColor);
            }
            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.
     */
    @Override
    public void mouseMoved(MouseEvent e) {
    }

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

    /**
     * Does nothing.
     * 
     * @param e the mouse event.
     */
    @Override
    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.
     */
    @Override
    public void paintComponent(Graphics g) {
        Color oldBackground = ((Graphics2D) g).getBackground();
        if (m_BackgroundColor != null) {
            ((Graphics2D) g).setBackground(m_BackgroundColor);
        }
        g.clearRect(0, 0, getSize().width, getSize().height);
        ((Graphics2D) g).setBackground(oldBackground);
        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; NOT USED
        Node r; // ,s; NOT USED
        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;
                if (m_NodeColor == null) {
                    acol = m_nodes[m_highlightNode].m_node.getColor();
                } else {
                    acol = m_NodeColor;
                }
                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

        if (m_NodeColor == null) {
            g.setColor(m_nodes[n].m_node.getColor());
        } else {
            g.setColor(m_NodeColor);
        }
        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);

        if (m_LineColor == null) {
            g.setColor(Color.black);
        } else {
            g.setColor(m_LineColor);
        }
        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

        // backup color
        Color oldColor = g.getColor();

        g.setPaintMode();
        if (m_FontColor == null) {
            g.setColor(Color.black);
        } else {
            g.setColor(m_FontColor);
        }
        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());
            }
        }

        // restore color
        g.setColor(oldColor);
    }

    /**
     * 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; NOT USED
        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) / (m_viewSize.width) * temp.width);
        np.height = (int) (e.height / 2
                - (((double) e.height / 2) - m_viewPos.height) / (m_viewSize.height) * 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 {
            weka.core.logging.Logger.log(weka.core.logging.Logger.Level.INFO, "Logging started");
            // 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 = Node.getCount(top,0); NOT USED
            // 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) {
            // ignored
        }
    }
}