I had accidentally broken the raceXML construction - fixed it.

Fixed a few issues where boat didn't have nextMark/previousMark times under certain circumstances.
LatestMessages wasn't actually notifying properly - fixed.
The various XML readers now treat a string constructor as containing file contents, not file name - this was how it was mostly already used.
Fixed some issues in RaceXMLReader, including where element.getChildNodes() was used instead of element.getElementsByName(...).
Boat: changes speed to a doubleProperty. Changed leg to a Property<Leg>.
Race.lastFps is now an IntegerProperty.
Added breaks to the case statements in VisualiserInput. Whoops.
ResizableRaceCanvas now handles drawing the race boundary. Tidied up code a bit.
Removed ResizableRaceMap.

Started refactoring Sparkline. Currently doesn't work.
This commit is contained in:
fjc40
2017-07-10 08:35:43 +12:00
parent abbbf70146
commit 7d3cf6ee80
38 changed files with 1414 additions and 775 deletions
@@ -1,7 +1,9 @@
package visualiser.Controllers;
import javafx.animation.AnimationTimer;
import javafx.application.Platform;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.fxml.FXML;
import javafx.scene.chart.LineChart;
@@ -10,13 +12,14 @@ import javafx.scene.layout.AnchorPane;
import javafx.scene.layout.GridPane;
import javafx.scene.layout.Pane;
import javafx.scene.layout.StackPane;
import javafx.util.Callback;
import network.Messages.Enums.RaceStatusEnum;
import shared.model.Leg;
import visualiser.app.VisualiserInput;
import shared.model.RaceClock;
import visualiser.model.Sparkline;
import visualiser.model.VisualiserBoat;
import visualiser.model.VisualiserRace;
import visualiser.model.*;
import java.net.URL;
import java.text.DecimalFormat;
import java.util.ResourceBundle;
/**
@@ -34,8 +37,15 @@ public class RaceController extends Controller {
*/
private VisualiserRace visualiserRace;
private ResizableRaceCanvas raceMap;
private ResizableRaceMap raceBoundaries;
/**
* An additional observable list of boats. This is used by the table view, to allow it to sort boats without effecting the race's own list of boats.
*/
private ObservableList<VisualiserBoat> tableBoatList;
/**
* The canvas that draws the race.
*/
private ResizableRaceCanvas raceCanvas;
/**
* The sparkline graph.
@@ -54,12 +64,13 @@ public class RaceController extends Controller {
@FXML private TableView<VisualiserBoat> boatInfoTable;
@FXML private TableColumn<VisualiserBoat, String> boatPlacingColumn;
@FXML private TableColumn<VisualiserBoat, String> boatTeamColumn;
@FXML private TableColumn<VisualiserBoat, String> boatMarkColumn;
@FXML private TableColumn<VisualiserBoat, String> boatSpeedColumn;
@FXML private TableColumn<VisualiserBoat, Leg> boatMarkColumn;
@FXML private TableColumn<VisualiserBoat, Number> boatSpeedColumn;
@FXML private LineChart<Number, Number> sparklineChart;
@FXML private AnchorPane annotationPane;
/**
* Ctor.
*/
@@ -68,10 +79,54 @@ public class RaceController extends Controller {
@Override
public void initialize(URL location, ResourceBundle resources) {
initialiseFpsToggle();
}
/**
* Initialises the various UI components to listen to the {@link #visualiserRace}.
*/
private void initialiseRace() {
//Fps display.
initialiseFps(this.visualiserRace);
//Need to add the included arrow pane to the arrowPane container.
initialiseArrow();
//Information table.
initialiseInfoTable(this.visualiserRace);
//Sparkline.
initialiseSparkline(this.visualiserRace);
//Race canvas.
initialiseRaceCanvas(this.visualiserRace);
//Race timezone label.
initialiseRaceTimezoneLabel(this.visualiserRace);
//Race clock.
initialiseRaceClock(this.visualiserRace);
//Start the race animation timer.
raceTimer();
}
/**
* Initialises the frame rate functionality. This allows for toggling the frame rate, and connect the fps label to the race's fps property.
* @param visualiserRace The race to connect the fps label to.
*/
private void initialiseFps(VisualiserRace visualiserRace) {
//On/off toggle.
initialiseFpsToggle();
//Label value.
initialiseFpsLabel(visualiserRace);
}
/**
* Initialises a listener for the fps toggle.
*/
@@ -90,53 +145,167 @@ public class RaceController extends Controller {
}
/**
* Updates the ResizableRaceCanvas (raceMap) with most recent data
*
* @param boats boats that are to be displayed in the race
* @param boatMarkers Markers for boats
* @see ResizableRaceCanvas
* Initialises the fps label to update when the race fps changes.
* @param visualiserRace The race to monitor the frame rate of.
*/
public void updateMap(ObservableList<Boat> boats, ObservableList<Marker> boatMarkers) {
raceMap.setBoats(boats);
raceMap.setBoatMarkers(boatMarkers);
raceMap.update();
raceBoundaries.draw();
//stop if the visualiser is no longer running
private void initialiseFpsLabel(VisualiserRace visualiserRace) {
visualiserRace.fpsProperty().addListener((observable, oldValue, newValue) -> {
Platform.runLater(() -> this.FPS.setText("FPS: " + newValue.toString()));
});
}
/**
* Updates the array listened by the TableView (boatInfoTable) that displays the boat information.
*
* Initialises the information table view to listen to a given race.
* @param race Race to listen to.
*/
public void setInfoTable(VisualiserRace race) {
boatInfoTable.setItems(race.getStartingBoats());
boatTeamColumn.setCellValueFactory(cellData -> cellData.getValue().getName());
boatSpeedColumn.setCellValueFactory(cellData -> cellData.getValue().getVelocityProp());
boatMarkColumn.setCellValueFactory(cellData -> cellData.getValue().getCurrentLegName());
boatPlacingColumn.setCellValueFactory(cellData -> cellData.getValue().positionProperty());
public void initialiseInfoTable(VisualiserRace race) {
//Copy list of boats.
this.tableBoatList = FXCollections.observableArrayList(race.getBoats());
//Set up table.
boatInfoTable.setItems(this.tableBoatList);
//Set up each column.
//Name.
boatTeamColumn.setCellValueFactory(
cellData -> cellData.getValue().nameProperty() );
//Speed.
boatSpeedColumn.setCellValueFactory(
cellData -> cellData.getValue().currentSpeedProperty() );
//Kind of ugly, but allows for formatting an observed speed.
boatSpeedColumn.setCellFactory(
//Callback object.
new Callback<TableColumn<VisualiserBoat, Number>, TableCell<VisualiserBoat, Number>>() {
//Callback function.
@Override
public TableCell<VisualiserBoat, Number> call(TableColumn<VisualiserBoat, Number> param) {
//We return a table cell that populates itself with a Number, and formats it.
return new TableCell<VisualiserBoat, Number>(){
//Function to update the cell text.
@Override
protected void updateItem(Number item, boolean empty) {
if (item != null) {
super.updateItem(item, empty);
setText(String.format("%.2fkn", item.doubleValue()));
}
}
};
}
} );
//Last mark.
boatMarkColumn.setCellValueFactory(
cellData -> cellData.getValue().legProperty() );
//Kind of ugly, but allows for turning an observed Leg into a string.
boatMarkColumn.setCellFactory(
//Callback object.
new Callback<TableColumn<VisualiserBoat, Leg>, TableCell<VisualiserBoat, Leg>>() {
//Callback function.
@Override
public TableCell<VisualiserBoat, Leg> call(TableColumn<VisualiserBoat, Leg> param) {
//We return a table cell that populates itself with a Leg's name.
return new TableCell<VisualiserBoat, Leg>(){
//Function to update the cell text.
@Override
protected void updateItem(Leg item, boolean empty) {
if (item != null) {
super.updateItem(item, empty);
setText(item.getName());
}
}
};
}
} );
//Current place within race.
boatPlacingColumn.setCellValueFactory(
cellData -> cellData.getValue().positionProperty() );
}
/**
* Creates and sets initial display for Sparkline for race positions.
* @param boats boats to display on the sparkline
* Initialises the {@link Sparkline}, and listens to a specified {@link VisualiserRace}.
* @param race The race to listen to.
*/
public void createSparkLine(ObservableList<VisualiserBoat> boats){
sparkline = new Sparkline(boats, legNum, sparklineChart);
private void initialiseSparkline(VisualiserRace race) {
this.sparkline = new Sparkline(race.getBoats(), race.getLegCount(), this.sparklineChart);
}
/**
* Initialises the {@link ResizableRaceCanvas}, provides the race to read data from.
* @param race Race to read data from.
*/
private void initialiseRaceCanvas(VisualiserRace race) {
//Create canvas.
raceCanvas = new ResizableRaceCanvas(race, arrow.getChildren().get(0));
//Set properties.
raceCanvas.setMouseTransparent(true);
raceCanvas.widthProperty().bind(canvasBase.widthProperty());
raceCanvas.heightProperty().bind(canvasBase.heightProperty());
//Draw it and show it.
raceCanvas.draw();
raceCanvas.setVisible(true);
//Add to scene.
canvasBase.getChildren().add(0, raceCanvas);
}
/**
* Intialises the race time zone label with the race's time zone.
* @param race The race to get time zone from.
*/
private void initialiseRaceTimezoneLabel(VisualiserRace race) {
timeZone.setText(race.getRaceClock().getTimeZone());
}
/**
* Updates the sparkline to display current boat positions.
* @param boatsInRace used for current boat positions.
* Initialises the race clock to listen to the specified race.
* @param race The race to listen to.
*/
public void updateSparkline(ObservableList<VisualiserBoat> boatsInRace){
sparkline.updateSparkline(boatsInRace);
private void initialiseRaceClock(VisualiserRace race) {
//RaceClock.duration isn't necessarily being changed in the javaFX thread, so we need to runlater the update.
race.getRaceClock().durationProperty().addListener((observable, oldValue, newValue) -> {
Platform.runLater(() -> {
timer.setText(newValue);
});
});
}
/**
* Displays a specified race.
* @param visualiserInput Object used to read packets from server.
@@ -147,92 +316,69 @@ public class RaceController extends Controller {
this.visualiserInput = visualiserInput;
this.visualiserRace = visualiserRace;
legNum = visualiserInput.getCourse().getLegs().size()-1;
makeArrow();
raceMap = new ResizableRaceCanvas(visualiserInput.getCourse());
raceMap.setMouseTransparent(true);
raceMap.widthProperty().bind(canvasBase.widthProperty());
raceMap.heightProperty().bind(canvasBase.heightProperty());
raceMap.draw();
raceMap.setVisible(true);
raceMap.setArrow(arrow.getChildren().get(0));
canvasBase.getChildren().add(0, raceMap);
raceBoundaries = new ResizableRaceMap(visualiserInput.getCourse());
raceBoundaries.setMouseTransparent(true);
raceBoundaries.widthProperty().bind(canvasBase.widthProperty());
raceBoundaries.heightProperty().bind(canvasBase.heightProperty());
raceBoundaries.draw();
raceBoundaries.setVisible(true);
canvasBase.getChildren().add(0, raceBoundaries);
initialiseRace();
//Display this controller.
race.setVisible(true);
timeZone.setText(raceClock.getTimeZone());
//RaceClock.duration isn't necessarily being changed in the javaFX thread, so we need to runlater the update.
raceClock.durationProperty().addListener((observable, oldValue, newValue) -> {
Platform.runLater(() -> {
timer.setText(newValue);
});
});
this.raceClock = raceClock;
raceMap.setRaceClock(raceClock);
//TODO move this list of colors somewhere more sensible.
StreamedRace newRace = new StreamedRace(visualiserInput, colours, this);
initializeFPS();
// set up annotation displays
new Annotations(annotationPane, raceMap);
new Thread((newRace)).start();
new Annotations(annotationPane, raceCanvas);
}
/**
* Finish Race View
* Transition from the race view to the finish view.
* @param boats boats there are in the race.
*/
public void finishRace(ObservableList<Boat> boats){
public void finishRace(ObservableList<VisualiserBoat> boats){
race.setVisible(false);
parent.enterFinish(boats);
}
/**
* Set the value for the fps label
*
* @param fps fps that the label will be updated to
*/
public void setFrames(String fps) {
FPS.setText((fps));
}
/**
* Set up FPS display at bottom of screen
* Adds the included arrow pane (see arrow.fxml) to the arrowPane (see race.fxml).
*/
private void initializeFPS() {
showFPS.setVisible(true);
showFPS.selectedProperty().addListener((ov, old_val, new_val) -> {
if (showFPS.isSelected()) {
FPS.setVisible(true);
} else {
FPS.setVisible(false);
}
});
}
private void makeArrow() {
private void initialiseArrow() {
arrowPane.getChildren().add(arrow);
}
public RaceClock getRaceClock() {
return raceClock;
/**
* Timer which monitors the race.
*/
private void raceTimer() {
new AnimationTimer() {
@Override
public void handle(long arg0) {
//Get the current race status.
RaceStatusEnum raceStatus = visualiserRace.getRaceStatusEnum();
//If the race has finished, go to finish view.
if (raceStatus == RaceStatusEnum.FINISHED) {
//Stop this timer.
stop();
//Hide this, and display the finish controller.
finishRace(visualiserRace.getBoats());
} else {
//Otherwise, render the canvas.
raceCanvas.drawRace();
//Sort the tableview. Doesn't automatically work for all columns.
boatInfoTable.sort();
//Update sparkline. TODO this should simply observe the boats.
sparkline.updateSparkline(visualiserRace.getBoats());
}
}
}.start();
}
}