Organization Chart
Here I am going to build google organization chart using spring boot framework.
Organization chart can be used to display hierarchical data of an organization, for example, you can display the employees and their managers in hierarchical order.
You can also find the organization hierarchy in your organization also. For example, you can see your manager, your manager’s manager and so on. Probably you can find this hierarchy in the MS Teams or MS Outlook.
Prerequisites
Java 19, Spring Boot 3.1.3/3.1.5, Maven 3.8.5, MySQL 8.1.0, Google Org Chart API
Project Setup
The following pom.xml file can be used for your maven based project:
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.roytuts</groupId>
<artifactId>spring-google-org-chart</artifactId>
<version>0.0.1-SNAPSHOT</version>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<maven.compiler.source>19</maven.compiler.source>
<maven.compiler.target>19</maven.compiler.target>
</properties>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>3.1.5</version>
</parent>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<dependency>
<groupId>com.mysql</groupId>
<artifactId>mysql-connector-j</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
MySQL Table And Data
The following SQL content is required for this example:
CREATE DATABASE IF NOT EXISTS `roytuts`;
USE `roytuts`;
CREATE TABLE IF NOT EXISTS `emp` (
`id` int unsigned NOT NULL AUTO_INCREMENT,
`name` varchar(50) COLLATE utf8mb4_unicode_ci DEFAULT NULL,
`designation` varchar(50) COLLATE utf8mb4_unicode_ci DEFAULT NULL,
`manager` varchar(50) COLLATE utf8mb4_unicode_ci DEFAULT '',
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=113 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
INSERT INTO `emp` (`id`, `name`, `designation`, `manager`) VALUES
(100, 'Steven', 'CEO', ''),
(101, 'Neena', 'President', 'Steven'),
(102, 'Lex', 'Vice President', 'Neena'),
(103, 'Alexander', 'Senior Manager', 'Lex'),
(104, 'Bruce', 'Manager', 'Alexander'),
(105, 'David', 'Manager', 'Alexander'),
(106, 'Diana', 'Associate', 'Bruce'),
(107, 'Nancy', 'Associate', 'David'),
(108, 'Daniel', 'Associate', 'Bruce'),
(109, 'John', 'Senior Manager', 'Lex'),
(110, 'Jose', 'Manager', 'John'),
(111, 'Luis', 'Associate', 'Jose'),
(112, 'Den', 'Trainee', 'Luis');
Application Config
The following application configuration is written into the application.properties file under src/main/resources folder.
spring.datasource.driverClassName=com.mysql.cj.jdbc.Driver
spring.datasource.url=jdbc:mysql://localhost/roytuts
spring.datasource.username=root
spring.datasource.password=root
I have put the database settings as per the spring standard naming conventions, so I do not need to create any datasource instance manually and spring boot will take care of this thing using the auto-configuration feature.
MySQL Table
The table structure and sample data are given below:
CREATE DATABASE IF NOT EXISTS `roytuts`;
USE `roytuts`;
CREATE TABLE IF NOT EXISTS `emp` (
`id` int unsigned NOT NULL AUTO_INCREMENT,
`name` varchar(50) COLLATE utf8mb4_unicode_ci DEFAULT NULL,
`designation` varchar(50) COLLATE utf8mb4_unicode_ci DEFAULT NULL,
`manager` varchar(50) COLLATE utf8mb4_unicode_ci DEFAULT '',
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=113 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
INSERT INTO `emp` (`id`, `name`, `designation`, `manager`) VALUES
(100, 'Steven', 'CEO', ''),
(101, 'Neena', 'President', 'Steven'),
(102, 'Lex', 'Vice President', 'Neena'),
(103, 'Alexander', 'Senior Manager', 'Lex'),
(104, 'Bruce', 'Manager', 'Alexander'),
(105, 'David', 'Manager', 'Alexander'),
(106, 'Diana', 'Associate', 'Bruce'),
(107, 'Nancy', 'Associate', 'David'),
(108, 'Daniel', 'Associate', 'Bruce'),
(109, 'John', 'Senior Manager', 'Lex'),
(110, 'Jose', 'Manager', 'John'),
(111, 'Luis', 'Associate', 'Jose'),
(112, 'Den', 'Trainee', 'Luis');
Entity Class
The following entity class will map the table in database.
@Entity
@Table(name = "emp")
public class Employee {
@Id
@Column
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Integer id;
@Column
private String name;
@Column
private String designation;
@Column
private String manager;
//getters and setters
}
DTO Class
The DTO class that corresponds to the above entity class:
public class EmpDto {
private Integer id;
private String name;
private String designation;
private String manager;
//getters and setters
}
The DTO class is required to handle request and response for the end users or clients. The entity class should not be actually used for request and response structures in the controller class. The entity class should be hidden from the client interaction code.
Entity and DTO Converter
The following class is required to convert entity class to DTO class:
public final class EmpDtoEntityConverter {
public static EmpDto getEmpDto(Employee employee) {
return new EmpDto(employee.getId(), employee.getName(), employee.getDesignation(), employee.getManager());
}
public static List<EmpDto> getEmpDtoList(List<Employee> employees) {
return employees.stream().map(e -> getEmpDto(e)).collect(Collectors.toList());
}
}
JPA Repository
Spring Data JPA API can be used to get advantage of its built-in methods to query database using minimal code written.
public interface EmployeeRepository extends JpaRepository<Employee, Integer> {
}
Service Class
The service class generally used to process business logic for the application.
@Service
public class EmployeeService {
@Autowired
private EmployeeRepository employeeRepository;
public List<EmpDto> getEmpList() {
List<Employee> employees = employeeRepository.findAll();
return EmpDtoEntityConverter.getEmpDtoList(employees);
}
}
Controller Class
The controller class will handle request and response for the clients:
@Controller
public class EmployeeController {
@Autowired
private EmployeeService employeeService;
@GetMapping("/orgChart")
public String orgChartData(Model model) {
List<EmpDto> empDtos = employeeService.getEmpList();
model.addAttribute("orgChartDataList", empDtos);
return "index";
}
}
Spring Boot Main Class
The spring boot main class will deploy the application into the embedded Tomcat server.
@SpringBootApplication
@EntityScan(basePackages = "com.roytuts.spring.google.orgchart.entity")
@EnableJpaRepositories(basePackages = "com.roytuts.spring.google.orgchart.repository")
public class App {
public static void main(String[] args) {
SpringApplication.run(App.class, args);
}
}
Front End Code
I am using Thymeleaf from spring boot framework to display the org chart on User Interface. The index.html file is kept under src/main/resources/templates folder.
<html xmlns:th="http://www.thymeleaf.org">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
<title>Google Org Chart using Spring</title>
<script type="text/javascript" src="https://www.gstatic.com/charts/loader.js"></script>
<script type="text/javascript" th:inline="javascript">
//Load data from server
var chartData = /*[[${orgChartDataList}]]*/'';
//Load chart package
google.charts.load('current', {'packages': ['orgchart']});
// Set a callback to run when the Google Visualization API is loaded.
google.charts.setOnLoadCallback(drawChart);
// Callback that creates and populates a data table,
// instantiates the chart, passes in the data and
// draws it.
function drawChart() {
// Create the data table.
var data = new google.visualization.DataTable();
data.addColumn('string', 'Name');
data.addColumn('string', 'Manager');
data.addColumn('string', 'ToolTip');
/*for(i = 0; i < chartData.length; i++) {
data.addRow([ chartData[i].name, chartData[i].manager, chartData[i].designation ]);
}*/
//Or
chartData.forEach(c => data.addRow([ c.name, c.manager, c.designation ]));
// Instantiate and draw the chart
var chart = new google.visualization.OrgChart(document.getElementById('chart_div'));
chart.draw(data, {'allowHtml':true});
}
</script>
</head>
<body>
<div style="width: 600px;">
<div id="chart_div"></div>
</div>
</body>
</html>
The package ‘orgchart’ is loaded for org chart.
google.charts.load('current', {packages: ['orgchart']});
The visualization’s class name is google.visualization.OrgChart
.
var visualization = new google.visualization.OrgChart(container);
A table with three string columns, where each row represents a node in the orgchart. Here are the three columns:
- Column 0 – The node ID. It should be unique among all nodes, and can include any characters, including spaces. This is shown on the node. You can specify a formatted value to show on the chart instead, but the unformatted value is still used as the ID.
- Column 1 – [optional] The ID of the parent node. This should be the unformatted value from column 0 of another row. Leave unspecified for a root node.
- Column 2 – [optional] Tool-tip text to show, when a user hovers over this node.
- Each node can have zero or one parent node, and zero or more child nodes.
Testing the Organization Chart
Accessing the URL http://localhost:8080/orgChart will display the organization chart as shown below: