Fixed sparkline not updating.

#story[1095]
main
fjc40 8 years ago
parent 5ae8393126
commit dc4610d6eb

@ -2,6 +2,8 @@ package shared.model;
import javafx.beans.property.Property; import javafx.beans.property.Property;
import javafx.beans.property.SimpleObjectProperty; import javafx.beans.property.SimpleObjectProperty;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import network.Messages.Enums.RaceStatusEnum; import network.Messages.Enums.RaceStatusEnum;
import network.Messages.Enums.RaceTypeEnum; import network.Messages.Enums.RaceTypeEnum;
import shared.dataInput.BoatDataSource; import shared.dataInput.BoatDataSource;
@ -37,6 +39,12 @@ public abstract class RaceState {
*/ */
private RegattaDataSource regattaDataSource; private RegattaDataSource regattaDataSource;
/**
* Legs in the race.
* We have this in a separate list so that it can be observed.
*/
private ObservableList<Leg> legs;
/** /**
@ -65,6 +73,9 @@ public abstract class RaceState {
*/ */
public RaceState() { public RaceState() {
//Legs.
this.legs = FXCollections.observableArrayList();
//Race clock. //Race clock.
this.raceClock = new RaceClock(ZonedDateTime.now()); this.raceClock = new RaceClock(ZonedDateTime.now());
@ -90,8 +101,9 @@ public abstract class RaceState {
* @param legs The new list of legs to use. * @param legs The new list of legs to use.
*/ */
protected void useLegsList(List<Leg> legs) { protected void useLegsList(List<Leg> legs) {
this.legs.setAll(legs);
//We add a "dummy" leg at the end of the race. //We add a "dummy" leg at the end of the race.
if (legs.size() > 0) { if (getLegs().size() > 0) {
getLegs().add(new Leg("Finish", getLegs().size())); getLegs().add(new Leg("Finish", getLegs().size()));
} }
} }
@ -126,6 +138,7 @@ public abstract class RaceState {
public void setRaceDataSource(RaceDataSource raceDataSource) { public void setRaceDataSource(RaceDataSource raceDataSource) {
this.raceDataSource = raceDataSource; this.raceDataSource = raceDataSource;
this.getRaceClock().setStartingTime(raceDataSource.getStartDateTime()); this.getRaceClock().setStartingTime(raceDataSource.getStartDateTime());
useLegsList(raceDataSource.getLegs());
} }
/** /**
@ -309,8 +322,8 @@ public abstract class RaceState {
* Returns the legs of the race. * Returns the legs of the race.
* @return Legs of the race. * @return Legs of the race.
*/ */
public List<Leg> getLegs() { public ObservableList<Leg> getLegs() {
return raceDataSource.getLegs(); return legs;
} }

@ -531,6 +531,7 @@ public class ResizableRaceCanvas extends ResizableCanvas {
* draws the line leg by leg * draws the line leg by leg
* @param legs the legs of a race * @param legs the legs of a race
* @param index the index of the current leg to use * @param index the index of the current leg to use
* @param legStartPoint The position the current leg.
* @return the end point of the current leg that has been drawn * @return the end point of the current leg that has been drawn
*/ */
private GPSCoordinate drawLineRounding(List<Leg> legs, int index, GPSCoordinate legStartPoint){ private GPSCoordinate drawLineRounding(List<Leg> legs, int index, GPSCoordinate legStartPoint){

@ -1,14 +1,18 @@
package visualiser.model; package visualiser.model;
import javafx.application.Platform; import javafx.application.Platform;
import javafx.collections.ListChangeListener;
import javafx.collections.ObservableList; import javafx.collections.ObservableList;
import javafx.collections.transformation.SortedList; import javafx.collections.transformation.SortedList;
import javafx.scene.chart.LineChart; import javafx.scene.chart.LineChart;
import javafx.scene.chart.NumberAxis; import javafx.scene.chart.NumberAxis;
import javafx.scene.chart.XYChart; import javafx.scene.chart.XYChart;
import javafx.scene.paint.Color; import javafx.scene.paint.Color;
import shared.model.Leg;
import java.util.HashMap;
import java.util.List; import java.util.List;
import java.util.Map;
/** /**
@ -32,10 +36,10 @@ public class Sparkline {
private ObservableList<VisualiserBoat> boats; private ObservableList<VisualiserBoat> boats;
/** /**
* The number of legs in the race. * Race legs to observe.
* Used to correctly scale the linechart. * We need to observe legs as they may be added after the sparkline is created if race.xml is received after this is created.
*/ */
private Integer legNum; private ObservableList<Leg> legs;
/** /**
@ -53,6 +57,14 @@ public class Sparkline {
*/ */
private NumberAxis yAxis; private NumberAxis yAxis;
/**
* A map between a boat and its data series in the sparkline.
* This is used so that we can remove a series when (or if) a boat is removed from the race.
*/
private Map<VisualiserBoat, XYChart.Series<Number, Number>> boatSeriesMap;
/** /**
* Constructor to set up initial sparkline (LineChart) object * Constructor to set up initial sparkline (LineChart) object
@ -62,12 +74,14 @@ public class Sparkline {
public Sparkline(VisualiserRaceState race, LineChart<Number,Number> sparklineChart) { public Sparkline(VisualiserRaceState race, LineChart<Number,Number> sparklineChart) {
this.race = race; this.race = race;
this.boats = new SortedList<>(race.getBoats()); this.boats = new SortedList<>(race.getBoats());
this.legNum = race.getLegCount(); this.legs = race.getLegs();
this.sparklineChart = sparklineChart; this.sparklineChart = sparklineChart;
this.yAxis = (NumberAxis) sparklineChart.getYAxis(); this.yAxis = (NumberAxis) sparklineChart.getYAxis();
this.xAxis = (NumberAxis) sparklineChart.getXAxis(); this.xAxis = (NumberAxis) sparklineChart.getXAxis();
this.boatSeriesMap = new HashMap<>();
createSparkline(); createSparkline();
} }
@ -79,50 +93,45 @@ public class Sparkline {
* Position numbers are displayed. * Position numbers are displayed.
*/ */
private void createSparkline() { private void createSparkline() {
// NOTE: Y axis is in negatives to display correct positions
//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. //We need to dynamically update the sparkline when boats are added/removed.
boat.legProperty().addListener( boats.addListener((ListChangeListener.Change<? extends VisualiserBoat> c) -> {
(observable, oldValue, newValue) -> {
//Get the data to plot. Platform.runLater(() -> {
List<VisualiserBoat> boatOrder = race.getLegCompletionOrder().get(oldValue);
//Find boat position in list.
int boatPosition = boatOrder.indexOf(boat) + 1;
//Get leg number. while (c.next()) {
int legNumber = oldValue.getLegNumber() + 1;
if (c.wasAdded()) {
for (VisualiserBoat boat : c.getAddedSubList()) {
addBoatSeries(boat);
}
//Create new data point for boat's position at the new leg. } else if (c.wasRemoved()) {
XYChart.Data<Number, Number> dataPoint = new XYChart.Data<>(legNumber, boatPosition); for (VisualiserBoat boat : c.getRemoved()) {
removeBoatSeries(boat);
}
}
//Add to series. }
Platform.runLater(() -> series.getData().add(dataPoint));
//Update height of y axis.
yAxis.setLowerBound(boats.size());
});
}); });
//Add to chart. legs.addListener((ListChangeListener.Change<? extends Leg> c) -> {
sparklineChart.getData().add(series); Platform.runLater(() -> xAxis.setUpperBound(race.getLegCount()));
});
//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()) + ";");
//Initialise chart for existing boats.
for (VisualiserBoat boat : boats) {
addBoatSeries(boat);
} }
sparklineChart.setCreateSymbols(false); sparklineChart.setCreateSymbols(false);
//Set x axis details //Set x axis details
@ -131,7 +140,7 @@ public class Sparkline {
xAxis.setTickLabelsVisible(false); xAxis.setTickLabelsVisible(false);
xAxis.setMinorTickVisible(false); xAxis.setMinorTickVisible(false);
xAxis.setLowerBound(0); xAxis.setLowerBound(0);
xAxis.setUpperBound(legNum + 2); xAxis.setUpperBound(race.getLegCount());
xAxis.setTickUnit(1); xAxis.setTickUnit(1);
//Set y axis details //Set y axis details
@ -148,6 +157,65 @@ public class Sparkline {
} }
/**
* Removes the data series for a given boat from the sparkline.
* @param boat Boat to remove series for.
*/
private void removeBoatSeries(VisualiserBoat boat) {
sparklineChart.getData().remove(boatSeriesMap.get(boat));
boatSeriesMap.remove(boat);
}
/**
* Creates a data series for a boat, and adds it to the sparkline.
* @param boat Boat to add series for.
*/
private void addBoatSeries(VisualiserBoat boat) {
//Create data series for 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);
//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()) + ";");
boatSeriesMap.put(boat, series);
}
/** /**
* Converts a color to a hex string, starting with a {@literal #} symbol. * Converts a color to a hex string, starting with a {@literal #} symbol.
* @param color The color to convert. * @param color The color to convert.

@ -97,7 +97,7 @@ public class VisualiserRaceState extends RaceState {
this.generateVisualiserBoats(this.boats, getBoatDataSource().getBoats(), raceDataSource.getParticipants(), unassignedColors); this.generateVisualiserBoats(this.boats, getBoatDataSource().getBoats(), raceDataSource.getParticipants(), unassignedColors);
} }
useLegsList(raceDataSource.getLegs()); initialiseLegCompletionOrder();
} }
/** /**
@ -125,14 +125,9 @@ public class VisualiserRaceState extends RaceState {
/** /**
* See {@link RaceState#useLegsList(List)}. * Initialises the {@link #legCompletionOrder} map.
* Also initialises the {@link #legCompletionOrder} map.
* @param legs The new list of legs to use.
*/ */
@Override public void initialiseLegCompletionOrder() {
public void useLegsList(List<Leg> legs) {
super.useLegsList(legs);
//Initialise the leg completion order map. //Initialise the leg completion order map.
for (Leg leg : getLegs()) { for (Leg leg : getLegs()) {
this.legCompletionOrder.put(leg, new ArrayList<>(this.boats.size())); this.legCompletionOrder.put(leg, new ArrayList<>(this.boats.size()));

Loading…
Cancel
Save