package ciips.animation.tree; import java.awt.*; import java.awt.font.*; import java.awt.geom.*; import ciips.animation.*; /** * This class holds the information regarding a node from the heap/complete * binary tree. This class implements the DrawingObj interface * and hence can be freely added to the drawing panel using the * addDrawingObj method. e.g.
 *	TreeNode node = new TreeNode();
 *	drawingPanel.addDrawingObj(node);
 * 
* Any added drawing object can be remove from the panel by using the * removeObj method. e.g.
 *	drawingPanel.removeObj(node);
 * 
* @see DrawingPanel#addDrawingObj * @see DrawingPanel#removeObj */ public class TreeNode extends Drawable { protected int weight; protected TreeNode left, right; protected int depth = -1; private boolean red; // Used by red-black trees public static final Color DEF_TREENODE_COL = Color.red; private Dimension node_size = new Dimension( 20, 30 ); /** * Attribute to indicate if the left branch is to be highlighted. */ protected boolean highlightLeft = false; /** * Attribute to indicate if the right branch is to be highlighted. */ protected boolean highlightRight = false; private static final boolean DEBUG = false; /** * Create a node with the left node as set and weight of the current node * set to 0. * @param node The left node of the newly created node. */ public TreeNode( TreeNode node ) { left = node; right = null; weight = 0; colour = DEF_TREENODE_COL; } /** * Create a new node with weight 0. */ public TreeNode() { this( 0 ); } /** * Create a new leaf node with label and weight as specified in the * parameters. * @param label The label of the new node. * @param weight The weight of the new node. */ public TreeNode(String label, int weight) { this( weight ); this.label = label; } // Constructor 2 /** * Create a new leaf node with the specified weight. * @param weight The weight of the new node. */ public TreeNode( int weight ) { label = null; this.weight = weight; left = right = null; colour = DEF_TREENODE_COL; } // Constructor 3 /** * Create a new leaf node with 0 weight and label as specified. * @param label Label of the new node. */ public TreeNode( String label ) { this( 0 ); this.label = new String(label); } // Constructor 4 /** * Set the weight of this node. * @param weight The weight to be assigned to this node. */ public void setWeight(int weight) { this.weight = weight; } /** * Set the label of this node. * @param label The label to be assigned to this node. */ public void setLabel(String label) { this.label = new String(label); } /** * Get the weight of this node. * @return Weight of this node. */ public int getWeight() { return weight; } /** * Get the label of this node. * @return Label of this node. */ public String getLabel() { return new String(label); } /** * Link the left branch of this node to the node passed in as the parameter. * @param node The new left child of this node. */ public void setLeft(TreeNode node) { left = node; } public void setLeftTreeNode(TreeNode node) { left = node; } /** * Link the right branch of this node to * the node passed in as the parameter. * @param node The new right child of this node. * */ public void setRight( TreeNode node ) { right = node; } public void setRightTreeNode(TreeNode node) { right = node; } /** * Get the left child of this node. * @return the left child this this node. */ public TreeNode getLeft() { return left; } public TreeNode getLeftTreeNode() { return left; } /** * Get the right child of this node. * @return the right child this this node. * */ public TreeNode getRight() { return right; } public TreeNode getRightTreeNode() { return right; } /** * Check if this node is a leaf node. A leaf node has both left and right * nodes null. * @return true if the node is a leaf node; false otherwise. */ public boolean isLeaf() { return ((right==null) && (left==null)); } public int getHeight() { int lh = 0, rh = 0; if ( left != null ) lh = left.getHeight(); if ( right != null ) rh = right.getHeight(); return (lh>rh)?lh:rh + 1; } /** * Get the depth of his node. * @return The depth of this node in a tree. */ public int getDepth() { return depth; } // Somewhat of a hack to allow the position of a new child to be // computed public static int last_dy, last_dx; /** Start at a node and set the positions for the sub-tree elements **/ public void setPosition( int x, int y, int dx, int dy ) { this.x = x; this.y = y; last_dx = dx; last_dy = dy; int dx2 = dx/2; if( left != null ) { left.setPosition( x-dx2, y+dy, dx2, dy ); } if( right != null ) { right.setPosition( x+dx2, y+dy, dx2, dy ); } } /** * Sets the depth of this node corresponding to the root node of the tree. * @param depth Depth of the node. */ public void setDepth(int depth) { this.depth = depth; } /** * Move the node and all its branches based on the parameters. * @param x The horizontal destination position of this node. * @param y The vertical destination position of this node. */ public void move(int x, int y) { int dx = x - this.x; int dy = y - this.y; moveTreeNode(dx, dy); } /** * Move the tree starting with node dx pixels to the right and dy * pixels down. * @param node The root node of the tree to be moved. * @param dx The change in x direction. * @param dy The change in y direction. */ public void moveTreeNode(int dx, int dy) { x += dx; y += dy; if (!isLeaf()) { if (getLeftTreeNode() != null) getLeftTreeNode().moveTreeNode(dx, dy); if (getRightTreeNode() != null) getRightTreeNode().moveTreeNode(dx, dy); } } static Font weight_font = new Font("Courier", Font.PLAIN, 12); static Font label_font = new Font("Courier", Font.BOLD, 12 ); /** * Assign some font instances to reduce initialization over during * redraw. */ public void initFonts(Font hugeFont, Font bigFont) { weight_font = hugeFont; label_font = bigFont; } protected Color labelColor = Color.yellow; /** * Set the color of the node. * @param nodeColor new color of the node. */ public void initColors(Color nodeColor) { setColour( nodeColor ); } public void setLabelColour( Color x ) { labelColor = x; } /** Adjust the node size by a factor * @param factor - expansion factor: factor > 1.0 => expansion, factor < 1.0 => shrinkage **/ public void expandSize( float factor ) { float x = (float)(node_size.width) * factor; node_size.width = Math.round( x ); x = (float)(node_size.height) * factor; node_size.height = Math.round( x ); } /** Return the current node size - used by other routines, eg, TreeEdge, * to control their drawing **/ public Dimension getNodeSize() { return node_size; } /** * This method draws the node on the corresponding graphical context * normally passed in from the drawing panel. */ public void draw( Graphics g ) { /* String representing weight */ String ws = "" + getWeight(); Graphics2D g2d = (Graphics2D)g; if( DEBUG ) System.out.println("TreeNode:draw [" + ws + "][" + ((label==null)?"~":label) + "]" ); /* Establish the size for the label */ FontRenderContext frc = g2d.getFontRenderContext(); TextLayout layout = new TextLayout( ws, weight_font, frc ); Rectangle2D bounds = layout.getBounds(); double text_height = 1.5*bounds.getHeight(); double text_width = bounds.getWidth()+4; double bly = y + (bounds.getY()); int text_left = (int)(x - text_width / 2); // bounds.setRect(bounds.getX()+x,bounds.getY()+y, // bounds.getWidth(),bounds.getHeight()); System.out.print("TreeNode:draw - bounds (" + bounds.getX() + "," + bly + ") [" + text_height + "," + text_width + "]"); System.out.println(" x,y (" + x + "," + y + ")" ); g.setColor( getCurrentColour() ); g.fillRect( text_left, y, (int)text_width, (int)text_height ); g.setColor(Color.black); g.drawRect( text_left, y, (int)text_width, (int)text_height ); if ( label != null ) { if (getLabel().length() > 0) { g.setColor( labelColor ); g.setFont( label_font ); g.drawString(getLabel(), x + (int)text_width, y + (int)text_height); } } g.setColor( Color.white ); // g.setFont( weight_font ); // g.drawString("" + getWeight(), x + 2, y+27); layout.draw(g2d, (float)(text_left+2), (float)(y+text_height) ); if( DEBUG ) System.out.println("TreeNode:draw [" + ws + "] exit" ); } } // class TreeNode