In this tutorial we are creating a very simple solution for displaying the schedule of an aircraft fleet.To install FlexGanttFX please follow the instructions found in 1. Installation.
View Model
Let's start by creating a view model for the Gantt chart. Our objects are Fleet, Aircraft, Crew, and Flight. Instances of Flight will be shown as a horizontal bar in the graphics area of the Gantt chart while the first three will be displayed in the rows of the tree table area. Fleet, Aircraft, and Crew share a common superclass called ModelObject, an extension of Row.
The Row class is being used to define a hierarchical data structure by the help of three type arguments: the first one specifies the type of the parent row, the second one the type of the children rows, and the third one the type of activities that will be shown on the right-hand side of the Gantt chart.
class ModelObject< P extends Row<?,?,?>, // Type of parent row C extends Row<?,?,?>, // Type of child rows A extends Activity> extends Row<P, C, A> { }
We can now pass ModelObject as a type argument when creating an instance of a GanttChart control. This informs the control that all rows will have this common supertype.
GanttChart<ModelObject<?,?,?> gantt = new GanttChart<>()
The Aircraft model class can be implemented as shown in the following code fragment, assuming that a fleet consists of several aircrafts, each aircraft having a crew, and flights being assigned to aircrafts and crews.
public class Aircraft extends ModelObject<Fleet, Crew, Flight> { public Aircraft(String name) { super(name); } }
The Flight class extends MutableActivityBase and might look like this:
public class Flight extends MutableActivityBase<FlightData> { public Flight(FlightData data) { super(data.getFlightName()); // the activity name setStartTime(data.getFlightDepartureTime()); // start / end times as java.time.Instant setEndTime(data.getFlightArrivalTime()); setUserObject(data); // a user object according to the type argument above } }
This class definies a flight as a mutable activity, which means that the flight can be edited by the user. We can also see that the activity gets its information from a domain object of type FlightData . Supporting a user object allows us to create a bridge between the domain model and the view model. All that is left to do now is to add the activities / the flights to the rows / the aircrafts. For this we can simply call the method Row.addActivity(Layer, Activity).
Activity Repositories
Rows do not store activities themselves, instead they are delegating all activity-related functionality to a repository of type ActivityRepository. The default repository is of type IntervalTreeRepository. Applications can implement their own repositories and register them by calling Row.setRepository() .
Layers
Layers are used to create groups of activities so that they can be shown / hidden together. In our example we want to group flights based on their service type (cargo, charter, training, etc...).
Layer cargoLayer = new Layer("Cargo"); Layer trainingLayer = new Layer("Training"); Layer charterLayer = new Layer("Charter"); gantt.getLayers().addAll(cargoLayer, trainingLayer, charterLayer); // make layers known to Gantt
Now the Gantt chart knows which layers it needs to rendere and we can create the link between the layers and the activities. This is done when we add the activities to the rows (here: add flights to aircrafts).
Flight flight1 = new Flight(); // a cargo flight Flight flight2 = new Flight(); // a training flight Flight flight3 = new Flight(); // a charter flight aircraft1.addActivity(cargoLayer, flight1); aircraft1.addActivity(trainingLayer, flight2); aircraft2.addActivity(charterLayer, flight3);
Intermediate Result
In the following code sample we are combining all of the steps from above.
import javafx.application.Application; import javafx.scene.Scene; import javafx.stage.Stage; import com.flexganttfx.model.Activity; import com.flexganttfx.model.Layer; import com.flexganttfx.model.Row; import com.flexganttfx.model.activity.MutableActivityBase; import com.flexganttfx.view.GanttChart; public class MyFirstGanttChart extends Application { /* * Common superclass of Fleet, Aircraft, and Crew. */ class ModelObject< P extends Row<?,?,?>, // Type of parent row C extends Row<?,?,?>, // Type of child rows A extends Activity> extends Row<P, C, A> { } class Fleet extends ModelObject<Row<?,?,?>, Aircraft, Activity> { } class Aircraft extends ModelObject<Fleet, Crew, Flight> { } class Flight extends MutableActivityBase<Object> { } @Override public void start(Stage stage) throws Exception { // Our root object. Fleet fleet = new Fleet(); fleet.setExpanded(true); // Create the control. GanttChart<ModelObject<?,?,?>> gantt = new GanttChart<>(fleet); // Layers based on service type. Layer cargoLayer = new Layer("Cargo"); Layer trainingLayer = new Layer("Training"); Layer charterLayer = new Layer("Charter"); gantt.getLayers().addAll(cargoLayer, trainingLayer, charterLayer); // Create the aircrafts. Aircraft aircraft1 = new Aircraft(); Aircraft aircraft2 = new Aircraft(); // Add the aircrafts to the fleet. fleet.getChildren().addAll(aircraft1, aircraft2); // Create the flights Flight flight1 = new Flight(); // a cargo flight Flight flight2 = new Flight(); // a training flight Flight flight3 = new Flight(); // a charter flight aircraft1.addActivity(cargoLayer, flight1); aircraft1.addActivity(trainingLayer, flight2); aircraft2.addActivity(charterLayer, flight3); Scene scene = new Scene(gantt); stage.setTitle("Fleet Schedule"); stage.setScene(scene); stage.centerOnScreen(); stage.sizeToScene(); stage.show(); } public static void main(String[] args) { Application.launch(args); } }
The image below shows what we will see when we run this code.
Activity Renderers
This result is not bad for just a few lines of code, however the rendering of the flights is not attractive at all. We can customize their apperance by registering a different ActivityRenderer for the activity type Flight . This is done by calling the method GraphicsBase.setActivityRenderer() where the graphics view is the control on the right-hand side of the Gantt chart. It is responsible for rendering all activities. We can add the following lines to our example from above.
GraphicsView <ModelObject<?, ?, ?>> graphics = gantt.getGraphics(); graphics.setActivityRenderer( Flight.class, GanttLayout.class, new ActivityBarRenderer<>(graphics, "FlightRenderer"));
This replaces the default activity renderer with a renderer that draws a fixed-height bar. Interesting about this code is that we are not only passing the activity type and the renderer instance but also a layout type. We don't want to spend too much time on layouts in the context of this quick start guide but let's just say that FlexGanttFX is capable of displaying activities in several different ways (as time bars, as chart entries, as agenda entries).
Our example now looks like this:
We can now add a GanttChartToolBar and a GanttChartStatusBar to the example. This allows us to perform actions on the chart and also to verify that the layers have been added properly. The following lines of code are needed for this.
BorderPane borderPane = new BorderPane(); borderPane.setTop(new GanttChartToolBar(gantt)); borderPane.setCenter(gantt); borderPane.setBottom(new GanttChartStatusBar(gantt)); Scene scene = new Scene(borderPane);
Our example will now look like this after clicking on the layers button in the toolbar.
Listening to Change
Now that we have visualized our data we obviously want to interact with it and we want to be informed about the changes that we make. Our activities are, by default, editable. This means we can drag them horizontally or vertically. To receive events we only need to register an ActivityEvent handler with the graphics view control by calling GraphicsBase.setOnActivityChanged().
graphics.setOnActivityChanged(evt -> System.out.println(evt));
When we run our application now we will see the following output in the console.
event type: DRAG_STARTED, time interval: 2014-04-17T21:45:00Z - 2014-04-22T21:30:00Z, value (chart value / percentage complete): 0.0, activity "null from 2014-04-18T12:15:00Z until 2014-04-23T12:00:00Z, user object = null", row = "Default", layer = "Training" event type: DRAG_ONGOING, time interval: 2014-04-17T21:45:00Z - 2014-04-22T21:30:00Z, value (chart value / percentage complete): 0.0, activity "null from 2014-04-18T12:15:00Z until 2014-04-23T12:00:00Z, user object = null", row = "Default", layer = "Training" event type: DRAG_FINISHED, time interval: 2014-04-17T21:45:00Z - 2014-04-22T21:30:00Z, value (chart value / percentage complete): 0.0, activity "null from 2014-04-18T03:00:00Z until 2014-04-23T02:45:00Z, user object = null", row = "Default", layer = "Training"
Please notice the three different event types DRAG_STARTED, DRAG_ONGOING, and DRAG_FINISHED The first one gets fired when the user initiates a drag, the second while while the drag is still in progress, and the third one when the drag has finished. This pattern can be observed in JavaFX itself and it was implemented throughout FlexGanttFX as well. Make sure to take a look at the various event types defined in the ActivityEvent class to find out how much information you can receive when the user performs editing operations.
Attachments:
fleet-schedule-customized.png (image/png)
fleet-schedule-layers.png (image/png)
airbus.jpg (image/jpeg)