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.
main
fjc40 9 years ago
parent 7d3cf6ee80
commit cab9bf9574

@ -59,6 +59,7 @@ public class Boat {
*/
private StringProperty positionInRace = new SimpleStringProperty();
/**
* The time, in milliseconds, that has elapsed during the current leg.
* TODO milliseconds
@ -222,9 +223,12 @@ public class Boat {
* @param currentLeg The new leg of the race the boat is in.
*/
public void setCurrentLeg(Leg currentLeg) {
this.currentLeg.setValue(currentLeg);
this.setTimeElapsedInCurrentLeg(0);
this.setDistanceTravelledInLeg(0);
}

@ -33,7 +33,7 @@ public class Constants {
* Frame periods are multiplied by this to get the amount of time a single frame represents.
* E.g., frame period = 20ms, scale = 5, frame represents 20 * 5 = 100ms, and so boats are simulated for 100ms, even though only 20ms actually occurred.
*/
public static final int RaceTimeScale = 25;
public static final int RaceTimeScale = 15;
/**
* The race pre-start time, in milliseconds. 3 minutes.

@ -163,7 +163,6 @@ public abstract class Race implements Runnable {
//Race clock.
this.raceClock = new RaceClock(this.raceDataSource.getStartDateTime());
//this.raceClock.run();//TODO looks like we may not actually need this.
//Race status.
this.setRaceStatusEnum(RaceStatusEnum.NOT_ACTIVE);

@ -3,10 +3,8 @@ package shared.model;
import com.github.bfsmith.geotimezone.TimeZoneLookup;
import com.github.bfsmith.geotimezone.TimeZoneResult;
import com.sun.istack.internal.Nullable;
import javafx.animation.AnimationTimer;
import javafx.beans.property.SimpleStringProperty;
import javafx.beans.property.StringProperty;
import shared.model.GPSCoordinate;
import visualiser.model.ResizableRaceCanvas;
import java.time.Duration;
@ -14,7 +12,6 @@ import java.time.LocalDateTime;
import java.time.ZoneId;
import java.time.ZonedDateTime;
import java.time.format.DateTimeFormatter;
import java.time.temporal.ChronoUnit;
import java.util.Date;
/**
@ -24,12 +21,7 @@ import java.util.Date;
* {@link visualiser.Controllers.RaceController} and the
* {@link visualiser.Controllers.StartController}.
*/
public class RaceClock implements Runnable {
/**
* The time that we last updated the current race time at.
*/
private long lastTime;
public class RaceClock {
/**
* The time zone of the race.
@ -72,7 +64,6 @@ public class RaceClock implements Runnable {
/**
* Format string used for current time.
* TODO probably need to fix this.
*/
private String currentTimeFormat = "'Current time:' HH:mm dd/MM/YYYY";
@ -100,7 +91,7 @@ public class RaceClock implements Runnable {
}
//TODO could potentially remove this, and the dependency, as it doesn't appear to be needed (regatta.xml has timezone already).
/**
* Returns the ZonedDateTime corresponding to a specified GPSCoordinate.
* @param gpsCoordinate The GPSCoordinate to lookup.
@ -113,17 +104,6 @@ public class RaceClock implements Runnable {
return LocalDateTime.now(zone).atZone(zone);
}
/**
* Starts the race clock.
*/
public void run() {
new AnimationTimer() {
@Override
public void handle(long now) {
updateTime();
}
}.start();
}
@ -148,20 +128,6 @@ public class RaceClock implements Runnable {
return utcTime.toInstant().atZone(this.zoneId);
}
/**
* Updates time by duration elapsed since last update.
*/
private void updateTime() {
//Get duration elapsed since last update.
Duration duration = Duration.of(System.currentTimeMillis() - this.lastTime, ChronoUnit.MILLIS);
//Add this duration to the current time.
ZonedDateTime newCurrentTime = this.currentTime.plus(duration);
setCurrentTime(newCurrentTime);
}
/**
* Returns the starting time of the race.
@ -286,8 +252,6 @@ public class RaceClock implements Runnable {
//Use it.
setCurrentTimeString(currentTimeString);
//Store the last time we updated the current time at.
this.lastTime = System.currentTimeMillis();
//Update the duration string.
updateDurationString();

@ -253,7 +253,8 @@ public class RaceController extends Controller {
* @param race The race to listen to.
*/
private void initialiseSparkline(VisualiserRace race) {
this.sparkline = new Sparkline(race.getBoats(), race.getLegCount(), this.sparklineChart);
//The race.getBoats() we are passing in is sorted by position in race inside the race class.
this.sparkline = new Sparkline(this.visualiserRace, this.sparklineChart);
}
@ -373,8 +374,6 @@ public class RaceController extends Controller {
//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());
}
}

@ -241,6 +241,7 @@ public class StartController extends Controller implements Observer {
@Override
public void handle(long arg0) {
//TODO instead of having an AnimationTimer checking the race status, we could provide a Property<RaceStatusEnum>, and connect a listener to that.
//Get the current race status.
RaceStatusEnum raceStatus = visualiserRace.getRaceStatusEnum();

@ -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.
yAxis.setTickMarkVisible(true);
yAxis.setTickLabelsVisible(true);
yAxis.setTickMarkVisible(true);
yAxis.setMinorTickVisible(true);
// hide minus number from displaying on axis
/* 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()){
return "";
}
else {
return String.format("%7.0f", -value.doubleValue());
}
}
});
}
//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());
/**
* 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));
}
} else {
System.out.println(" is bad");//TEMP
return "";
// 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.

Loading…
Cancel
Save