RaceClock no has run(), or is runnable, as it wasn't needed.

Refactored Sparkline a bit. Now listens to our data model instead of being manually updated. Boat position labels don't work currently, though.
VisualiserRace now also tracks the order in which boats pass each leg.
This commit is contained in:
fjc40
2017-07-11 02:04:53 +12:00
parent 7d3cf6ee80
commit cab9bf9574
9 changed files with 155 additions and 133 deletions
@@ -1,12 +1,14 @@
package visualiser.model;
import javafx.application.Platform;
import javafx.collections.ObservableList;
import javafx.fxml.FXML;
import javafx.scene.chart.LineChart;
import javafx.scene.chart.NumberAxis;
import javafx.scene.chart.XYChart;
import javafx.scene.paint.Color;
import java.util.List;
/**
* Class to process and modify a sparkline display. This display keeps visual
@@ -18,6 +20,11 @@ import javafx.scene.paint.Color;
*/
public class Sparkline {
/**
* The race to observe.
*/
private VisualiserRace race;
/**
* The boats to observe.
*/
@@ -29,43 +36,39 @@ public class Sparkline {
*/
private Integer legNum;
//TODO comment
private Integer sparkLineNumber = 0;
/**
* The linchart to plot sparklines on.
*/
@FXML LineChart<Number, Number> sparklineChart;
private LineChart<Number, Number> sparklineChart;
/**
* The x axis of the sparkline chart.
*/
@FXML NumberAxis xAxis;
private NumberAxis xAxis;
/**
* The y axis of the sparkline chart.
*/
@FXML NumberAxis yAxis;
private NumberAxis yAxis;
/**
* Constructor to set up initial sparkline (LineChart) object
* @param boats boats to display on the sparkline
* @param legNum total number of legs in the race
* @param sparklineChart javaFX LineChart for the sparkline
* @param race The race to listen to.
* @param sparklineChart JavaFX LineChart for the sparkline.
*/
public Sparkline(ObservableList<VisualiserBoat> boats, Integer legNum,
LineChart<Number,Number> sparklineChart) {
this.boats = boats;
public Sparkline(VisualiserRace race, LineChart<Number,Number> sparklineChart) {
this.race = race;
this.boats = race.getBoats();
this.legNum = race.getLegCount();
this.sparklineChart = sparklineChart;
this.legNum = legNum;
this.yAxis = (NumberAxis) sparklineChart.getYAxis();
this.xAxis = (NumberAxis) sparklineChart.getXAxis();
createSparkline();
//TODO refactor
//what we probably want to do is listen to the boats list, and when it updates, we update an observable list of series with new data. the linechart listens to the list of series.
}
@@ -74,105 +77,105 @@ public class Sparkline {
* A data series for each boat in the race is added.
* Position numbers are displayed.
*/
public void createSparkline(){
private void createSparkline() {
// NOTE: Y axis is in negatives to display correct positions
// all boats start in 'last' place
for (int i = 0; i< boats.size(); i++){
XYChart.Series<Number, Number> series = new XYChart.Series();
series.getData().add(new XYChart.Data(0, -boats.size()));
series.getData().add(new XYChart.Data(0, -boats.size()));
//For each boat...
for (VisualiserBoat boat : this.boats) {
//Create data series for each boat.
XYChart.Series<Number, Number> series = new XYChart.Series<>();
//All boats start in "last" place.
series.getData().add(new XYChart.Data<>(0, boats.size()));
//Listen for changes in the boat's leg - we only update the graph when it changes leg.
boat.legProperty().addListener(
(observable, oldValue, newValue) -> {
//Get the data to plot.
List<VisualiserBoat> boatOrder = race.getLegCompletionOrder().get(oldValue);
//Find boat position in list.
int boatPosition = boatOrder.indexOf(boat) + 1;
//Get leg number.
int legNumber = oldValue.getLegNumber() + 1;
//Create new data point for boat's position at the new leg.
XYChart.Data<Number, Number> dataPoint = new XYChart.Data<>(legNumber, boatPosition);
//Add to series.
Platform.runLater(() -> series.getData().add(dataPoint));
});
//Add to chart.
sparklineChart.getData().add(series);
sparklineChart.getData().get(i).getNode().setStyle("-fx-stroke: " +
"" + colourToHex(boats.get(i).getColor()) + ";");
//Color using boat's color. We need to do this after adding the series to a chart, otherwise we get null pointer exceptions.
series.getNode().setStyle("-fx-stroke: " + colourToHex(boat.getColor()) + ";");
}
sparklineChart.setCreateSymbols(false);
// set x axis details
//Set x axis details
xAxis.setAutoRanging(false);
xAxis.setTickMarkVisible(false);
xAxis.setTickLabelsVisible(false);
xAxis.setMinorTickVisible(false);
xAxis.setUpperBound((boats.size()+1)*legNum);
xAxis.setTickUnit((boats.size()+1)*legNum);
xAxis.setLowerBound(0);
xAxis.setUpperBound(legNum + 2);
xAxis.setTickUnit(1);
// set y axis details
yAxis.setLowerBound(-(boats.size()+1));
//Set y axis details
yAxis.setLowerBound(boats.size() + 1);
yAxis.setUpperBound(0);
yAxis.setAutoRanging(false);
yAxis.setLabel("Position in Race");
yAxis.setTickUnit(1);
yAxis.setTickMarkVisible(false);
yAxis.setMinorTickVisible(false);
yAxis.setTickUnit(-1);//Negative tick reverses the y axis.
// hide minus number from displaying on axis
yAxis.setTickMarkVisible(true);
yAxis.setTickLabelsVisible(true);
yAxis.setTickMarkVisible(true);
yAxis.setMinorTickVisible(true);
/* TODO FIX currently this doesn't work - I broke it :(
TODO only 0 and 7 get passed in to it for some reason
//Hide minus number from displaying on y axis.
yAxis.setTickLabelFormatter(new NumberAxis.DefaultFormatter(yAxis) {
@Override
public String toString(Number value) {
if ((Double)value == 0.0
|| (Double)value < -boats.size()){
//We only label the values between [1,boats.size()].
System.out.print("y axis: " + value.doubleValue());//TEMP remove
if ((value.doubleValue() >= 1)
&& (value.doubleValue() <= boats.size())) {
System.out.println(" is good");//TEMP
return String.format("%7.0f", value.doubleValue());
} else {
System.out.println(" is bad");//TEMP
return "";
}
else {
return String.format("%7.0f", -value.doubleValue());
}
}
});
}
/**
* Updates the sparkline to display current boat positions.
* New points are plotted to represent each boat when required.
* @param boatsInRace current position of the boats in race
*/
public void updateSparkline(ObservableList<VisualiserBoat> boatsInRace){
int placingVal = boatsInRace.size();
sparkLineNumber++;
for (int i = boatsInRace.size() - 1; i >= 0; i--){
for (int j = boats.size() - 1; j >= 0; j--){
if (boatsInRace.get(i)== boats.get(j)){
// when a boat is on its first leg
if (boatsInRace.get(i).getCurrentLeg().getLegNumber()==0){
// adjust boats latest point on X axis
sparklineChart.getData().get(j).getData().get(1)
.setXValue(sparkLineNumber);
}
// when a boat first enters its second leg
else if (boatsInRace.get(i).getCurrentLeg().getLegNumber
()==1 && sparklineChart.getData().get(j).getData
().size()==2){
// adjust boats position from start mark
sparklineChart.getData().get(j).getData().get(1)
.setYValue(-placingVal);
sparklineChart.getData().get(j).getData().get(1)
.setXValue(sparkLineNumber);
sparklineChart.getData().get(j).getData().add(new XYChart.Data<>
(sparkLineNumber, -placingVal));
}
// plot new point for boats current position
else {
sparklineChart.getData().get(j).getData().add
(new XYChart.Data<>(sparkLineNumber, -placingVal));
}
placingVal-=1;
}
}
}
*/
}
/**
* Converts a color to a hex string, starting with a {@literal #} symbol.
* @param color
* @return
* @param color The color to convert.
* @return Hex string of the color (e.g., {@literal "#11AB4C"}).
*/
private String colourToHex(Color color) {
private static String colourToHex(Color color) {
return String.format( "#%02X%02X%02X",
(int)( color.getRed() * 255 ),
(int)( color.getGreen() * 255 ),
@@ -1,5 +1,7 @@
package visualiser.model;
import javafx.beans.property.SimpleStringProperty;
import javafx.beans.property.StringProperty;
import javafx.scene.paint.Color;
import network.Messages.Enums.BoatStatusEnum;
import shared.model.Azimuth;
@@ -58,6 +60,7 @@ public class VisualiserBoat extends Boat {
/**
* Constructs a boat object from a given boat and color.
*
@@ -107,6 +110,8 @@ public class VisualiserBoat extends Boat {
*/
public void addTrackPoint(GPSCoordinate coordinate) {
//TODO bugfix: instead of creating a track point every #trackPointTimeInterval milliseconds of real time, we should base it off of race time, so that we will create a consistent number of track points regardless of what the race time scale is.
//Get current time.
long currentTime = System.currentTimeMillis();
@@ -18,6 +18,7 @@ import shared.model.*;
import java.time.Duration;
import java.time.ZonedDateTime;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
@@ -40,6 +41,13 @@ public class VisualiserRace extends Race {
private ObservableList<Mark> boatMarkers;
/**
* Maps between a Leg to a list of boats, in the order that they finished the leg.
* Used by the Sparkline to ensure it has correct information.
*/
private Map<Leg, List<VisualiserBoat>> legCompletionOrder = new HashMap<>();
/**
* Constructs a race object with a given RaceDataSource, BoatDataSource, and RegattaDataSource and receives events from LatestMessages.
@@ -58,6 +66,12 @@ public class VisualiserRace extends Race {
this.boatMarkers = FXCollections.observableArrayList(boatDataSource.getMarkerBoats().values());
//Initialise the leg completion order map.
for (Leg leg : this.legs) {
this.legCompletionOrder.put(leg, new ArrayList<>(this.boats.size()));
}
}
@@ -216,8 +230,7 @@ public class VisualiserRace extends Race {
if (legNumber >= 1 && legNumber < legs.size()) {
if (boat.getCurrentLeg() != legs.get(legNumber)) {
boat.setCurrentLeg(legs.get(legNumber));
boat.setTimeAtLastMark(this.raceClock.getCurrentTime());
boatFinishedLeg(boat, legs.get(legNumber));
}
}
@@ -238,6 +251,23 @@ public class VisualiserRace extends Race {
}
/**
* Updates a boat's leg to a specified leg. Also records the order in which the boat passed the leg.
* @param boat The boat to update.
* @param leg The leg to use.
*/
private void boatFinishedLeg(VisualiserBoat boat, Leg leg) {
//Record order in which boat finished leg.
this.legCompletionOrder.get(boat.getCurrentLeg()).add(boat);
//Update boat.
boat.setCurrentLeg(leg);
boat.setTimeAtLastMark(this.raceClock.getCurrentTime());
}
/**
* Updates all of the marker boats based on messages received.
* @param boatMarkers The list of marker boats.
@@ -354,14 +384,23 @@ public class VisualiserRace extends Race {
*/
private void updateBoatPositions(ObservableList<VisualiserBoat> boats) {
//Sort boats.
sortBoatsByPosition(boats);
for (VisualiserBoat boat: boats) {
boat.setPosition(Integer.toString(boats.indexOf(boat) + 1));
//Assign new positions.
for (int i = 0; i < boats.size(); i++) {
VisualiserBoat boat = boats.get(i);
if ((boat.getStatus() == BoatStatusEnum.DNF) || (boat.getStatus() == BoatStatusEnum.PRESTART) || (boat.getCurrentLeg().getLegNumber() < 0)) {
if ((boat.getStatus() == BoatStatusEnum.DNF) || (boat.getStatus() == BoatStatusEnum.PRESTART) || (boat.getCurrentLeg().getLegNumber() < 0))
boat.setPosition("-");
} else {
boat.setPosition(Integer.toString(i + 1));
}
}
}
/**
@@ -396,6 +435,14 @@ public class VisualiserRace extends Race {
return boats;
}
/**
* Returns the order in which boats completed each leg. Maps the leg to a list of boats, ordered by the order in which they finished the leg.
* @return Leg completion order for each leg.
*/
public Map<Leg, List<VisualiserBoat>> getLegCompletionOrder() {
return legCompletionOrder;
}
/**
* Takes an estimated time an event will occur, and converts it to the
* number of seconds before the event will occur.