Java tutorial
/* * 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 } } }