2. Tutorial

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. 

Model Object
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.

Typed Gantt Chart
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.

Row Type: Aircraft
public class Aircraft extends ModelObject<Fleet, Crew, Flight> { 
  public Aircraft(String name) { 
    super(name); 
  } 
}

The Flight class extends MutableActivityBase and might look like this:

Activity Type: Flight
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...).

Layers
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).

Adding Activities / 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);

Intermediate Result

In the following code sample we are combining all of the steps from above.

Aircraft Gantt Chart
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.

Registering an Activity Renderer
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.

Status- and Toolbar
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().

Receiving Activity Events
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.