Tree Visualization and MVC
Paul Ngugi
Posted on July 23, 2024
You can use recursion to display a binary tree.
One challenge facing the data-structure course is to motivate students. Displaying a binary tree graphically will not only help you understand the working of a binary tree but perhaps also stimulate your interest in programming. This section introduces the techniques to visualize binary trees. You can also apply visualization techniques to other projects.
How do you display a binary tree? It is a recursive structure, so you can display a binary tree using recursion. You can simply display the root, then display the two subtrees recursively. The techniques for displaying the Sierpinski triangle (SierpinskiTriangle.java) can be applied to displaying a binary tree. For simplicity, we assume the keys are positive integers less than 100. The codes below give the program, and Figure below shows some sample runs of the program.
package application;
import javafx.application.Application;
import javafx.geometry.Pos;
import javafx.stage.Stage;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.control.Label;
import javafx.scene.control.TextField;
import javafx.scene.layout.BorderPane;
import javafx.scene.layout.HBox;
public class BSTAnimation extends Application {
@Override // Override the start method in the Application class
public void start(Stage primaryStage) {
BST<Integer> tree = new BST<>(); // Create a tree
BorderPane pane = new BorderPane();
BTView view = new BTView(tree); // Create a BTView
pane.setCenter(view);
TextField tfKey = new TextField();
tfKey.setPrefColumnCount(3);
tfKey.setAlignment(Pos.BASELINE_RIGHT);
Button btInsert = new Button("Insert");
Button btDelete = new Button("Delete");
HBox hBox = new HBox(5);
hBox.getChildren().addAll(new Label("Enter a key: "), tfKey, btInsert, btDelete);
hBox.setAlignment(Pos.CENTER);
pane.setBottom(hBox);
btInsert.setOnAction(e -> {
int key = Integer.parseInt(tfKey.getText());
if (tree.search(key)) { // key is in the tree already
view.displayTree();
view.setStatus(key + " is already in the tree");
} else {
tree.insert(key); // Insert a new key
view.displayTree();
view.setStatus(key + " is inserted in the tree");
}
});
btDelete.setOnAction(e -> {
int key = Integer.parseInt(tfKey.getText());
if (!tree.search(key)) { // key is not in the tree
view.displayTree();
view.setStatus(key + " is not in the tree");
} else {
tree.delete(key); // Delete a key
view.displayTree();
view.setStatus(key + " is deleted from the tree");
}
});
// Create a scene and place the pane in the stage
Scene scene = new Scene(pane, 450, 250);
primaryStage.setTitle("BSTAnimation"); // Set the stage title
primaryStage.setScene(scene); // Place the scene in the stage
primaryStage.show(); // Display the stage
}
public static void main(String[] args) {
Application.launch(args);
}
}
package application;
import javafx.scene.layout.Pane;
import javafx.scene.paint.Color;
import javafx.scene.shape.Circle;
import javafx.scene.shape.Line;
import javafx.scene.text.Text;
public class BTView extends Pane {
private BST<Integer> tree = new BST<>();
private double radius = 15; // Tree node radius
private double vGap = 50; // Gap between two levels in a tree
BTView(BST<Integer> tree) {
this.tree = tree;
setStatus("Tree is empty");
}
public void setStatus(String msg) {
getChildren().add(new Text(20, 20, msg));
}
public void displayTree() {
this.getChildren().clear(); // Clear the pane
if (tree.getRoot() != null) {
// Display tree recursively
displayTree(tree.getRoot(), getWidth() / 2, vGap, getWidth() / 4);
}
}
/** Display a subtree rooted at position (x, y) */
private void displayTree(BST.TreeNode<Integer> root, double x, double y, double hGap) {
if (root.left != null) {
// Draw a line to the left node
getChildren().add(new Line(x - hGap, y + vGap, x, y));
// Draw the left subtree recursively
displayTree(root.left, x - hGap, y + vGap, hGap / 2);
}
if (root.right != null) {
// Draw a line to the right node
getChildren().add(new Line(x + hGap, y + vGap, x, y));
// Draw the right subtree recursively
displayTree(root.right, x + hGap, y + vGap, hGap / 2);
}
// Display a node
Circle circle = new Circle(x, y, radius);
circle.setFill(Color.WHITE);
circle.setStroke(Color.BLACK);
getChildren().addAll(circle, new Text(x - 4, y + 4, root.element + ""));
}
}
In the code, BSTAnimation.java, a tree is created (line 15) and a tree view is placed in the pane (line 19). After a new key is inserted into the tree (line 37), the tree is repainted (line 38) to reflect the change. After a key is deleted (line 49), the tree is repainted (line 50) to reflect the change.
In the code, BTView.java, the node is displayed as a circle with radius 15 (line 47). The distance between two levels in the tree is defined in vGap 50 (line 26). hGap (line 31) defines the distance between two nodes horizontally. This value is reduced by half (hGap / 2) in the next level when the displayTree method is called recursively (lines 43, 50). Note that vGap is not changed in the tree.
The method displayTree is recursively invoked to display a left subtree (lines 32β37) and a right subtree (lines 39β44) if a subtree is not empty. A line is added to the pane to connect two nodes (lines 34, 41). Note that the method first adds the lines to the pane and then adds the circle into the pane (line 50) so that the circles will be painted on top of the lines to achieve desired visual effects.
The program assumes that the keys are integers. You can easily modify the program with a generic type to display keys of characters or short strings.
Tree visualization is an example of the model-view-controller (MVC) software architecture. This is an important architecture for software development. The model is for storing and handling data. The view is for visually presenting the data. The controller handles the user interaction with the model and controls the view, as shown in Figure below.
The MVC architecture separates data storage and handling from the visual representation of the data. It has two major benefits:
- It makes multiple views possible so that data can be shared through the same model. For example, you can create a new view that displays the tree with the root on the left and tree grows horizontally to the right.
- It simplifies the task of writing complex applications and makes the components scalable and easy to maintain. Changes can be made to the view without affecting the model, and vice versa.
Posted on July 23, 2024
Join Our Newsletter. No Spam, Only the good stuff.
Sign up to receive the latest update from our blog.