weka.gui.treevisualizer.TreeVisualizer Maven / Gradle / Ivy
Show all versions of weka-dev Show documentation
/*
* 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 .
*/
/*
* 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.
*
*
* To work this class simply create an instance of it.
*
*
* Assign it to a window or other such object.
*
*
* Resize it to the desired size.
*
*
*
* When using the Displayer hold the left mouse button to drag the tree around.
*
*
* Click the left mouse button with ctrl to shrink the size of the tree by half.
*
*
* 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.
*
*
*
* Click the right mouse button to bring up a menu.
*
* Most options are self explanatory.
*
*
* Select Auto Scale to set the tree to it's optimal display size.
*
* @author Malcolm Ware ([email protected])
* @version $Revision: 15301 $
*/
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 nodes 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 edges 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 n 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 frames)
* 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
}
}
}