├── img └── ganttchart.png ├── GanttChartFX ├── src │ └── main │ │ ├── java │ │ ├── module-info.java │ │ └── com │ │ │ └── flyf │ │ │ ├── App.java │ │ │ ├── GanttChartActivity.java │ │ │ └── GanttChartViewController.java │ │ └── resources │ │ └── com │ │ └── flyf │ │ ├── ganttchartview.fxml │ │ └── default.css └── pom.xml ├── LICENSE └── README.md /img/ganttchart.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hry625/GanttChartFX/HEAD/img/ganttchart.png -------------------------------------------------------------------------------- /GanttChartFX/src/main/java/module-info.java: -------------------------------------------------------------------------------- 1 | module com.flyf { 2 | requires javafx.controls; 3 | requires javafx.fxml; 4 | 5 | opens com.flyf to javafx.fxml; 6 | exports com.flyf; 7 | } -------------------------------------------------------------------------------- /GanttChartFX/src/main/resources/com/flyf/ganttchartview.fxml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 16 | 17 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021 flyf 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /GanttChartFX/src/main/java/com/flyf/App.java: -------------------------------------------------------------------------------- 1 | package com.flyf; 2 | 3 | import javafx.application.Application; 4 | import javafx.fxml.FXMLLoader; 5 | import javafx.scene.Parent; 6 | import javafx.scene.Scene; 7 | import javafx.stage.Stage; 8 | 9 | import java.io.IOException; 10 | 11 | /** 12 | * JavaFX App 13 | */ 14 | public class App extends Application { 15 | 16 | private static Scene scene; 17 | 18 | @Override 19 | public void start(Stage stage) throws IOException { 20 | scene = new Scene(loadFXML("ganttchartview"), 640, 480); 21 | scene.getStylesheets().add(getClass().getResource("default.css").toExternalForm()); 22 | 23 | stage.setScene(scene); 24 | stage.show(); 25 | } 26 | 27 | static void setRoot(String fxml) throws IOException { 28 | scene.setRoot(loadFXML(fxml)); 29 | } 30 | 31 | private static Parent loadFXML(String fxml) throws IOException { 32 | FXMLLoader fxmlLoader = new FXMLLoader(App.class.getResource(fxml + ".fxml")); 33 | return fxmlLoader.load(); 34 | } 35 | 36 | public static void main(String[] args) { 37 | launch(); 38 | } 39 | 40 | } -------------------------------------------------------------------------------- /GanttChartFX/src/main/resources/com/flyf/default.css: -------------------------------------------------------------------------------- 1 | .gantt-chart-week-column { 2 | -fx-text-fill: #000; 3 | -fx-font-weight: normal; 4 | -fx-font-size: 0.8em; 5 | } 6 | 7 | .gantt-chart-day-column { 8 | -fx-text-fill: #000; 9 | -fx-font-weight: normal; 10 | -fx-font-size: 0.7em; 11 | } 12 | 13 | .gantt-chart-day-column-weekend { 14 | -fx-background-color: #e9ecef; 15 | -fx-fill: #e56767; 16 | -fx-text-fill: #e56767; 17 | -fx-font-weight: normal; 18 | -fx-font-size: 0.8em; 19 | } 20 | 21 | .gantt-chart-day-column-weekday { 22 | -fx-background-color: #fff; 23 | -fx-text-fill: #3b4654; 24 | -fx-font-weight: normal; 25 | -fx-font-size: 0.8em; 26 | } 27 | 28 | .gantt-chart-cell-empty { 29 | -fx-background-color: #fff; 30 | } 31 | 32 | .gantt-chart-cell-holiday { 33 | -fx-background-color: #e56767; 34 | } 35 | 36 | .gantt-chart-cell { 37 | -fx-background-color: #e0effa; 38 | -fx-text-fill: #e0effa; 39 | } 40 | 41 | .gantt-chart-cell1 { 42 | -fx-background-color: #1f77b4; 43 | -fx-text-fill: #1f77b4; 44 | } 45 | 46 | .gantt-chart-cell2 { 47 | -fx-background-color: #2ca02c; 48 | -fx-text-fill: #2ca02c; 49 | } 50 | 51 | .gantt-chart-cell3 { 52 | -fx-background-color: #d62728; 53 | -fx-text-fill: #d62728; 54 | } 55 | 56 | .gantt-chart-cell4 { 57 | -fx-background-color: #8c564b; 58 | -fx-text-fill: #8c564b; 59 | } 60 | 61 | .gantt-chart-cell5 { 62 | -fx-background-color: #bcbd22; 63 | -fx-text-fill: #bcbd22; 64 | } 65 | 66 | .gantt-chart-cell6 { 67 | -fx-background-color: #17becf; 68 | -fx-text-fill: #17becf; 69 | } 70 | 71 | .gantt-chart-cell7 { 72 | -fx-background-color: #ff7f0e; 73 | -fx-text-fill: #ff7f0e; 74 | } -------------------------------------------------------------------------------- /GanttChartFX/pom.xml: -------------------------------------------------------------------------------- 1 | 3 | 4.0.0 4 | com.flyf 5 | GanttChartFX 6 | 1.0-SNAPSHOT 7 | 8 | UTF-8 9 | 11 10 | 11 11 | 12 | 13 | 14 | org.openjfx 15 | javafx-controls 16 | 13 17 | 18 | 19 | org.openjfx 20 | javafx-fxml 21 | 13 22 | 23 | 24 | 25 | 26 | 27 | org.apache.maven.plugins 28 | maven-compiler-plugin 29 | 3.8.0 30 | 31 | 11 32 | 33 | 34 | 35 | org.openjfx 36 | javafx-maven-plugin 37 | 0.0.4 38 | 39 | com.flyf.App 40 | 41 | 42 | 43 | 44 | -------------------------------------------------------------------------------- /GanttChartFX/src/main/java/com/flyf/GanttChartActivity.java: -------------------------------------------------------------------------------- 1 | package com.flyf; 2 | 3 | import java.time.LocalDate; 4 | 5 | public class GanttChartActivity { 6 | private String title; 7 | private LocalDate startDate; 8 | private LocalDate endDate; 9 | private int topLevelID; 10 | private int middleLevelID; 11 | private int bottomLevelID; 12 | 13 | public GanttChartActivity(String title, LocalDate startDate, LocalDate endDate, int topLevelID, int middleLevelID, 14 | int bottomLevelID) { 15 | this.title = title; 16 | this.startDate = startDate; 17 | this.endDate = endDate; 18 | this.topLevelID = topLevelID; 19 | this.middleLevelID = middleLevelID; 20 | this.bottomLevelID = bottomLevelID; 21 | } 22 | 23 | public String getTitle() { 24 | return title; 25 | } 26 | 27 | public void setTitle(String title) { 28 | this.title = title; 29 | } 30 | 31 | public LocalDate getStartDate() { 32 | return startDate; 33 | } 34 | 35 | public void setStartDate(LocalDate startDate) { 36 | this.startDate = startDate; 37 | } 38 | 39 | public LocalDate getEndDate() { 40 | return endDate; 41 | } 42 | 43 | public void setEndDate(LocalDate endDate) { 44 | this.endDate = endDate; 45 | } 46 | 47 | public int getTopLevelID() { 48 | return topLevelID; 49 | } 50 | 51 | public void setTopLevelID(int topLevelID) { 52 | this.topLevelID = topLevelID; 53 | } 54 | 55 | public int getMiddleLevelID() { 56 | return middleLevelID; 57 | } 58 | 59 | public void setMiddleLevelID(int middleLevelID) { 60 | this.middleLevelID = middleLevelID; 61 | } 62 | 63 | public int getBottomLevelID() { 64 | return bottomLevelID; 65 | } 66 | 67 | public void setBottomLevelID(int bottomLevelID) { 68 | this.bottomLevelID = bottomLevelID; 69 | } 70 | 71 | } 72 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # GanttChartFX 2 | A GanttChart table written by JavaFX 3 | ![Gantt chart sample](/img/ganttchart.png) 4 | 5 | 6 | ## How to modify the Gantt chart to fit your need: 7 | 1. You may want to change the projectActivity to fit your project. 8 | 2. Using convertActivityToGanttChartActivity() function to convert your own activity class to fit GanttChartActivity. 9 | 3. Modify the default.css file to style the Gantt chart as you prefer. 10 | 11 | 12 | ## Known issue 13 | You may find the date part of activity row becomes empty after you collapse and re-extend the children activity rows. 14 | I have no clue how to solve this problem. 15 | If someone figure out how to fix it, please let me know. 16 | 17 | 18 | ## Special Thanks 19 | For Altansukh Tumenjargal and his excellent work with the css file styling. 20 | 21 | 22 | # License 23 | MIT License 24 | 25 | Copyright (c) 2021 flyf 26 | 27 | Permission is hereby granted, free of charge, to any person obtaining a copy 28 | of this software and associated documentation files (the "Software"), to deal 29 | in the Software without restriction, including without limitation the rights 30 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 31 | copies of the Software, and to permit persons to whom the Software is 32 | furnished to do so, subject to the following conditions: 33 | 34 | The above copyright notice and this permission notice shall be included in all 35 | copies or substantial portions of the Software. 36 | 37 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 38 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 39 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 40 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 41 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 42 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 43 | SOFTWARE. -------------------------------------------------------------------------------- /GanttChartFX/src/main/java/com/flyf/GanttChartViewController.java: -------------------------------------------------------------------------------- 1 | package com.flyf; 2 | 3 | import java.net.URL; 4 | import java.time.DayOfWeek; 5 | import java.time.LocalDate; 6 | import java.time.format.DateTimeFormatter; 7 | import java.time.temporal.ChronoUnit; 8 | import java.time.temporal.WeekFields; 9 | import java.util.ArrayList; 10 | import java.util.Locale; 11 | import java.util.ResourceBundle; 12 | 13 | import javafx.fxml.FXML; 14 | import javafx.fxml.Initializable; 15 | import javafx.scene.control.Label; 16 | import javafx.scene.control.TreeItem; 17 | import javafx.scene.control.TreeTableCell; 18 | import javafx.scene.control.TreeTableColumn; 19 | import javafx.scene.control.TreeTableView; 20 | import javafx.scene.control.cell.TreeItemPropertyValueFactory; 21 | import javafx.scene.paint.Color; 22 | import javafx.util.Callback; 23 | 24 | public class GanttChartViewController implements Initializable { 25 | private static int columnCount = 0; 26 | 27 | ArrayList activityList = new ArrayList<>(); 28 | ArrayList topLevelList; 29 | ArrayList midLevelList; 30 | ArrayList bottomLevelList; 31 | GanttChartActivity projectActivity; 32 | private static LocalDate calenderStartDate; 33 | private static LocalDate calenderEndDate; 34 | private static int numberOfCalenderDays; 35 | 36 | @FXML 37 | private Label titleLabel; 38 | @FXML 39 | private TreeTableView ganttChartTreeTableView; 40 | 41 | @Override 42 | public void initialize(URL arg0, ResourceBundle arg1) { 43 | // Create a place holder activity for the project. 44 | GanttChartActivity projectActivity = new GanttChartActivity("Project", LocalDate.of(2021, 01, 01), 45 | LocalDate.of(2021, 03, 01), 0, 0, 0); 46 | 47 | if (projectActivity.getStartDate().getDayOfWeek().equals(DayOfWeek.MONDAY)) { 48 | calenderStartDate = projectActivity.getStartDate(); 49 | } else { 50 | calenderStartDate = projectActivity.getStartDate().with(DayOfWeek.MONDAY); 51 | } 52 | if (projectActivity.getEndDate().getDayOfWeek().equals(DayOfWeek.SUNDAY)) { 53 | calenderEndDate = projectActivity.getEndDate(); 54 | } else { 55 | calenderEndDate = projectActivity.getEndDate().plusWeeks(1).with(DayOfWeek.SUNDAY); 56 | } 57 | 58 | numberOfCalenderDays = (int) ChronoUnit.DAYS.between(calenderStartDate, calenderEndDate) + 1; 59 | 60 | topLevelList = new ArrayList<>(); 61 | midLevelList = new ArrayList<>(); 62 | bottomLevelList = new ArrayList<>(); 63 | 64 | TreeItem projectItem = new TreeItem<>(projectActivity); 65 | convertActivityToGanttChartActivity(); 66 | 67 | for (GanttChartActivity topGanttChartActivity : topLevelList) { 68 | TreeItem topLevelItem = new TreeItem<>(topGanttChartActivity); 69 | for (GanttChartActivity middleGanttChartActivity : midLevelList) { 70 | if (middleGanttChartActivity.getTopLevelID() == topGanttChartActivity.getTopLevelID()) { 71 | TreeItem middleLevelItem = new TreeItem<>(middleGanttChartActivity); 72 | for (GanttChartActivity bottomGanttChartActivity : bottomLevelList) { 73 | if (middleGanttChartActivity.getTopLevelID() == bottomGanttChartActivity.getTopLevelID() 74 | && middleGanttChartActivity.getMiddleLevelID() == bottomGanttChartActivity 75 | .getMiddleLevelID()) { 76 | TreeItem bottomLevelItem = new TreeItem<>(bottomGanttChartActivity); 77 | middleLevelItem.getChildren().add(bottomLevelItem); 78 | middleLevelItem.setExpanded(true); 79 | } 80 | } 81 | topLevelItem.getChildren().add(middleLevelItem); 82 | topLevelItem.setExpanded(true); 83 | } 84 | } 85 | projectItem.getChildren().add(topLevelItem); 86 | projectItem.setExpanded(true); 87 | 88 | } 89 | 90 | TreeTableColumn treeTableColumn1 = new TreeTableColumn<>("Name"); 91 | treeTableColumn1.setPrefWidth(180.0); 92 | treeTableColumn1.setMinWidth(180.0); 93 | TreeTableColumn treeTableColumn2 = new TreeTableColumn<>("Start Date"); 94 | treeTableColumn2.setPrefWidth(80); 95 | treeTableColumn2.setMinWidth(80.0); 96 | TreeTableColumn treeTableColumn3 = new TreeTableColumn<>("End Date"); 97 | treeTableColumn3.setPrefWidth(80.0); 98 | treeTableColumn3.setMinWidth(80.0); 99 | 100 | treeTableColumn1.setCellValueFactory(new TreeItemPropertyValueFactory<>("title")); 101 | treeTableColumn2.setCellValueFactory(new TreeItemPropertyValueFactory<>("startDate")); 102 | treeTableColumn3.setCellValueFactory(new TreeItemPropertyValueFactory<>("endDate")); 103 | 104 | ganttChartTreeTableView.getColumns().add(treeTableColumn1); 105 | ganttChartTreeTableView.getColumns().add(treeTableColumn2); 106 | ganttChartTreeTableView.getColumns().add(treeTableColumn3); 107 | 108 | LocalDate currentDate = calenderStartDate; 109 | TreeTableColumn currentWeekColumn; 110 | Locale locale = new Locale("EN"); 111 | while (currentDate.isBefore(calenderEndDate)) { 112 | 113 | int currentWeek = currentDate.get(WeekFields.of(locale).weekOfYear()); 114 | int currentYear = currentDate.getYear(); 115 | 116 | String firstDayOfWeekString = currentDate.format(DateTimeFormatter.ofPattern("LLL d")); 117 | 118 | currentWeekColumn = new TreeTableColumn<>( 119 | currentYear + " Week " + (currentWeek) + " (" + firstDayOfWeekString + ")"); 120 | currentWeekColumn.getStyleClass().add("gantt-chart-week-column"); 121 | 122 | LocalDate tempDate = currentDate; 123 | 124 | for (int i = 0; i < 7; i++) { 125 | 126 | TreeTableColumn currentDateColumn = new TreeTableColumn<>( 127 | tempDate.format(DateTimeFormatter.ofPattern("dd"))); 128 | 129 | if (tempDate.getDayOfWeek().equals(DayOfWeek.SATURDAY)) { 130 | currentDateColumn.getStyleClass().add("gantt-chart-day-column-weekend"); 131 | } else if (tempDate.getDayOfWeek().equals(DayOfWeek.SUNDAY)) { 132 | currentDateColumn.getStyleClass().add("gantt-chart-day-column-weekend"); 133 | currentDateColumn.setStyle("-fx-border-color: transparent red transparent transparent"); 134 | 135 | } else { 136 | currentDateColumn.getStyleClass().add("gantt-chart-day-column-weekday"); 137 | } 138 | 139 | currentWeekColumn.getColumns().add(currentDateColumn); 140 | currentDateColumn.setCellFactory( 141 | new Callback, TreeTableCell>() { 142 | @Override 143 | public TreeTableCell call( 144 | TreeTableColumn param) { 145 | return new TreeTableCell() { 146 | 147 | @Override 148 | protected void updateItem(String item, boolean empty) { 149 | setText(null); 150 | getStyleClass().clear(); 151 | super.updateItem(item, empty); 152 | GanttChartActivity activity = getTreeTableRow().getItem(); 153 | int columnIndex = columnCount % numberOfCalenderDays; 154 | if (activity != null) { 155 | int daysBeforeActivityStart = (int) ChronoUnit.DAYS 156 | .between(calenderStartDate, activity.getStartDate()); 157 | int daysUntilActivityEnd = (int) ChronoUnit.DAYS.between(calenderStartDate, 158 | activity.getEndDate()); 159 | 160 | int rowIndex = getTreeTableRow().getIndex() % 7 + 1; 161 | 162 | if (columnIndex >= daysBeforeActivityStart 163 | && columnIndex <= daysUntilActivityEnd) { 164 | int dayOffSets = (int) ChronoUnit.DAYS.between(calenderStartDate, 165 | projectActivity.getStartDate()); 166 | setText(Integer.toString(columnIndex - dayOffSets)); 167 | setTextFill(Color.GREEN); 168 | getStyleClass().add("gantt-chart-cell" + rowIndex); 169 | 170 | } else if (columnIndex % 7 == 6 || columnIndex % 7 == 5) { 171 | 172 | getStyleClass().add("gantt-chart-day-column-weekend"); 173 | 174 | } else { 175 | getStyleClass().add("gantt-chart-day-column-weekday"); 176 | } 177 | } else if (columnIndex % 7 == 6 || columnIndex % 7 == 5) { 178 | getStyleClass().add("gantt-chart-day-column-weekend"); 179 | 180 | } 181 | columnCount++; 182 | if (columnCount == numberOfCalenderDays) { 183 | columnCount = 0; 184 | } 185 | 186 | } 187 | 188 | }; 189 | } 190 | }); 191 | tempDate = tempDate.plusDays(1); 192 | } 193 | 194 | ganttChartTreeTableView.getColumns().add(currentWeekColumn); 195 | currentDate = currentDate.plusDays(7); 196 | } 197 | 198 | ganttChartTreeTableView.setRoot(projectItem); 199 | 200 | } 201 | 202 | public void convertActivityToGanttChartActivity() { 203 | // fake data: 204 | GanttChartActivity activity1 = new GanttChartActivity("Test Activity", LocalDate.of(2021, 01, 02), 205 | LocalDate.of(2021, 02, 01), 1, 0, 0); 206 | GanttChartActivity activity2 = new GanttChartActivity("Test Activity", LocalDate.of(2021, 01, 02), 207 | LocalDate.of(2021, 01, 20), 1, 1, 0); 208 | GanttChartActivity activity3 = new GanttChartActivity("Test Activity", LocalDate.of(2021, 01, 02), 209 | LocalDate.of(2021, 01, 14), 1, 1, 1); 210 | GanttChartActivity activity4 = new GanttChartActivity("Test Activity", LocalDate.of(2021, 01, 15), 211 | LocalDate.of(2021, 02, 01), 1, 2, 0); 212 | GanttChartActivity activity5 = new GanttChartActivity("Test Activity", LocalDate.of(2021, 02, 02), 213 | LocalDate.of(2021, 02, 15), 1, 3, 0); 214 | GanttChartActivity activity6 = new GanttChartActivity("Test Activity", LocalDate.of(2021, 01, 02), 215 | LocalDate.of(2021, 02, 01), 2, 0, 0); 216 | GanttChartActivity activity7 = new GanttChartActivity("Test Activity", LocalDate.of(2021, 01, 02), 217 | LocalDate.of(2021, 02, 20), 3, 0, 0); 218 | activityList.add(activity1); 219 | activityList.add(activity2); 220 | activityList.add(activity3); 221 | activityList.add(activity4); 222 | activityList.add(activity5); 223 | activityList.add(activity6); 224 | activityList.add(activity7); 225 | // You could convert your activity class to the ganttchart activity in this 226 | // function 227 | // I use arraylist of GanttChartActivity for convinience, you can change 228 | // activitylist as an arraylist of your own activity class 229 | // I am using topLevelID, midLevelID and bottomLevelID to indicate the 230 | // relationship of different activities: 231 | // -Activity 1 1.0.0 232 | // --subActivity 1.1.0 233 | // ---grandChildActivity 1.1.1 234 | 235 | for (GanttChartActivity activity : activityList) { 236 | if (activity.getMiddleLevelID() == 0 && activity.getBottomLevelID() == 0) { 237 | topLevelList.add(activity); 238 | } else if (activity.getBottomLevelID() == 0) { 239 | midLevelList.add(activity); 240 | } else { 241 | bottomLevelList.add(activity); 242 | } 243 | } 244 | 245 | } 246 | 247 | } 248 | --------------------------------------------------------------------------------