diff --git a/mock/src/test/java/seng302/DataInput/BoatXMLReaderTest.java b/mock/src/test/java/seng302/DataInput/BoatXMLReaderTest.java index 5bf8b7b2..a5c85964 100644 --- a/mock/src/test/java/seng302/DataInput/BoatXMLReaderTest.java +++ b/mock/src/test/java/seng302/DataInput/BoatXMLReaderTest.java @@ -11,7 +11,8 @@ import java.io.IOException; import java.util.ArrayList; import java.util.List; -import static org.testng.Assert.*; +import static org.junit.Assert.assertEquals; +//import static org.testng.Assert.*; /** * Created by cbt24 on 10/05/17. diff --git a/mock/src/test/java/seng302/Model/CompoundMarkTest.java b/mock/src/test/java/seng302/Model/CompoundMarkTest.java index 00035995..45a91579 100644 --- a/mock/src/test/java/seng302/Model/CompoundMarkTest.java +++ b/mock/src/test/java/seng302/Model/CompoundMarkTest.java @@ -3,8 +3,9 @@ package seng302.Model; import org.junit.Ignore; import org.junit.Test; +import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertTrue; -import static org.testng.AssertJUnit.assertEquals; +//import static org.testng.AssertJUnit.assertEquals; /** * Created by esa46 on 29/03/17. diff --git a/visualiser/src/main/java/seng302/Controllers/RaceController.java b/visualiser/src/main/java/seng302/Controllers/RaceController.java index 106a0089..89d49e32 100644 --- a/visualiser/src/main/java/seng302/Controllers/RaceController.java +++ b/visualiser/src/main/java/seng302/Controllers/RaceController.java @@ -3,15 +3,18 @@ package seng302.Controllers; 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.control.*; import javafx.scene.layout.GridPane; +import javafx.scene.paint.Color; import seng302.Mock.StreamedRace; import seng302.Model.*; import seng302.VisualiserInput; import java.net.URL; -import java.util.ArrayList; -import java.util.ResourceBundle; +import java.util.*; /** * Created by fwy13 on 15/03/2017. @@ -21,9 +24,13 @@ public class RaceController extends Controller { //user saved data for annotation display private ArrayList presetAnno; - + private ArrayList startBoats; + private Integer sparkLineNumber = 0; private ResizableRaceCanvas raceMap; private ResizableRaceMap raceBoundaries; + private ArrayList colours; + private Map boatColours = new HashMap<>(); + private int legNum; @FXML SplitPane race; @FXML CheckBox showFPS; @FXML CheckBox showBoatPath; @@ -41,6 +48,9 @@ public class RaceController extends Controller { @FXML TableColumn boatTeamColumn; @FXML TableColumn boatMarkColumn; @FXML TableColumn boatSpeedColumn; + @FXML LineChart sparklineChart; + @FXML NumberAxis xAxis; + @FXML NumberAxis yAxis; /** * Updates the ResizableRaceCanvas (raceMap) with most recent data @@ -76,6 +86,7 @@ public class RaceController extends Controller { @Override public void initialize(URL location, ResourceBundle resources) { //listener for fps + startBoats = new ArrayList<>(); showFPS.selectedProperty().addListener((ov, old_val, new_val) -> { if (showFPS.isSelected()) { FPS.setVisible(true); @@ -85,6 +96,64 @@ public class RaceController extends Controller { }); } + /** + * Creates and sets initial display for Sparkline for race positions. + * A data series for each boat in the race is added. + * Position numbers are displayed. + * + * @param boats boats to display on the sparkline + */ + public void createSparkLine(ObservableList boats){ + // NOTE: Y axis is in negatives to display correct positions + + makeColours(); + startBoats.addAll(boats); + mapBoatColours(); + + // all boats start in 'last' place + for (int i=0; i series = new XYChart.Series(); + series.getData().add(new XYChart.Data(0, -startBoats.size())); + series.getData().add(new XYChart.Data(0, -startBoats.size())); + sparklineChart.getData().add(series); + sparklineChart.getData().get(i).getNode().setStyle("-fx-stroke: " + + ""+boatColours.get(startBoats.get(i).getSourceID())+";"); + } + + sparklineChart.setCreateSymbols(false); + + // set x axis details + xAxis.setAutoRanging(false); + xAxis.setTickMarkVisible(false); + xAxis.setTickLabelsVisible(false); + xAxis.setMinorTickVisible(false); + xAxis.setUpperBound(startBoats.size()*legNum); + xAxis.setTickUnit(startBoats.size()*legNum); + + + // set y axis details + yAxis.setLowerBound(-(startBoats.size()+1)); + yAxis.setUpperBound(0); + yAxis.setAutoRanging(false); + yAxis.setLabel("Position in Race"); + yAxis.setTickUnit(1); + yAxis.setTickMarkVisible(false); + yAxis.setMinorTickVisible(false); + // hide minus number from displaying on axis + yAxis.setTickLabelFormatter(new NumberAxis.DefaultFormatter(yAxis) { + @Override + public String toString(Number value) { + if ((Double)value == 0.0 + || (Double)value < -startBoats.size()){ + return ""; + } + else { + return String.format("%7.0f", -value.doubleValue()); + } + } + }); + } + /** * Initializes and runs the race, based on the user's chosen scale factor * Currently uses an example racecourse @@ -95,7 +164,7 @@ public class RaceController extends Controller { public void startRace(VisualiserInput visualiserInput, RaceClock raceClock) { StreamedRace newRace = new StreamedRace(visualiserInput, this); //newRace.initialiseBoats(); - + legNum = visualiserInput.getCourse().getLegs().size()-1; raceMap = new ResizableRaceCanvas(visualiserInput.getCourse()); raceMap.setMouseTransparent(true); raceMap.widthProperty().bind(canvasBase.widthProperty()); @@ -163,6 +232,77 @@ public class RaceController extends Controller { }); } + /** + * Updates the sparkline to display current boat positions. + * New points are plotted to represent each boat when required. + * + * @param boatsInRace used for current boat positions. + */ + public void updateSparkline(ObservableList boatsInRace){ + int placingVal = boatsInRace.size(); + sparkLineNumber++; + + for (int i = boatsInRace.size() - 1; i >= 0; i--){ + for (int j = startBoats.size() - 1; j >= 0; j--){ + if (boatsInRace.get(i)==startBoats.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; + } + } + } +// xAxis.setUpperBound(sparkLineNumber); +// xAxis.setTickUnit(sparkLineNumber); + } + + private void makeColours() { + colours = new ArrayList<>(Arrays.asList( + colourToHex(Color.BLUEVIOLET), + colourToHex(Color.BLACK), + colourToHex(Color.RED), + colourToHex(Color.ORANGE), + colourToHex(Color.DARKOLIVEGREEN), + colourToHex(Color.LIMEGREEN), + colourToHex(Color.PURPLE), + colourToHex(Color.DARKGRAY), + colourToHex(Color.YELLOW) + )); + } + + private void mapBoatColours() { + int currentColour = 0; + for (Boat boat : startBoats) { + if (!boatColours.containsKey(boat.getSourceID())) { + boatColours.put(boat.getSourceID(), colours.get(currentColour)); + } + currentColour = (currentColour + 1) % colours.size(); + } + } + /** * Set up boat annotations */ @@ -213,4 +353,11 @@ public class RaceController extends Controller { } }); } + + private String colourToHex(Color color) { + return String.format( "#%02X%02X%02X", + (int)( color.getRed() * 255 ), + (int)( color.getGreen() * 255 ), + (int)( color.getBlue() * 255 ) ); + } } diff --git a/visualiser/src/main/java/seng302/Mock/StreamedRace.java b/visualiser/src/main/java/seng302/Mock/StreamedRace.java index 2ca0fd5c..12b13639 100644 --- a/visualiser/src/main/java/seng302/Mock/StreamedRace.java +++ b/visualiser/src/main/java/seng302/Mock/StreamedRace.java @@ -76,6 +76,7 @@ public class StreamedRace implements Runnable { * @param timeElapsed Time that has elapse since the start of the the race. */ private void checkPosition(Boat boat, long timeElapsed) { + boolean legChanged = false; StreamedCourse raceData = visualiserInput.getCourse(); BoatStatus boatStatusMessage = visualiserInput.getBoatStatusMap().get(boat.getSourceID()); if (boatStatusMessage != null) { @@ -84,7 +85,10 @@ public class StreamedRace implements Runnable { int legNumber = boatStatusMessage.getLegNumber(); if (legNumber >= 1 && legNumber < legs.size()) { - boat.setCurrentLeg(legs.get(legNumber)); + if (boat.getCurrentLeg() != legs.get(legNumber)){ + boat.setCurrentLeg(legs.get(legNumber)); + legChanged = true; + } } if (boatStatusEnum == BoatStatusEnum.RACING) { @@ -98,8 +102,11 @@ public class StreamedRace implements Runnable { //System.out.println("Boat finished"); } } - //Update the boat display table in the GUI to reflect the leg change - updatePositions(); + if (legChanged) { + //Update the boat display table in the GUI to reflect the leg change + updatePositions(); + controller.updateSparkline(startingBoats); + } } /** @@ -153,6 +160,7 @@ public class StreamedRace implements Runnable { */ public void run() { setControllerListeners(); + Platform.runLater(() -> controller.createSparkLine(startingBoats)); initialiseBoats(); startRaceStream(); } @@ -189,6 +197,7 @@ public class StreamedRace implements Runnable { public void handle(long arg0) { if (boatsFinished < startingBoats.size()) { boatsFinished = 0; + //controller.updateSparkline(startingBoats); totalTimeElapsed = System.currentTimeMillis() - timeRaceStarted; for (Boat boat : startingBoats) { @@ -206,6 +215,7 @@ public class StreamedRace implements Runnable { } } //System.out.println(boatsFinished + ":" + startingBoats.size()); + } else { controller.finishRace(startingBoats); stop(); diff --git a/visualiser/src/main/java/seng302/Model/ResizableRaceCanvas.java b/visualiser/src/main/java/seng302/Model/ResizableRaceCanvas.java index b4815d22..e7b53d6f 100644 --- a/visualiser/src/main/java/seng302/Model/ResizableRaceCanvas.java +++ b/visualiser/src/main/java/seng302/Model/ResizableRaceCanvas.java @@ -10,9 +10,7 @@ import seng302.Mock.StreamedCourse; import seng302.RaceDataSource; import seng302.RaceMap; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.List; +import java.util.*; /** * This creates a JavaFX Canvas that is fills it's parent. @@ -31,6 +29,7 @@ public class ResizableRaceCanvas extends ResizableCanvas { private List colours; private final List markers; private final RaceDataSource raceData; + private Map boatColours = new HashMap<>(); public ResizableRaceCanvas(RaceDataSource raceData) { super(); @@ -54,6 +53,7 @@ public class ResizableRaceCanvas extends ResizableCanvas { */ public void setBoats(List boats) { this.boats = boats; + mapBoatColours(); } @@ -276,27 +276,27 @@ public class ResizableRaceCanvas extends ResizableCanvas { * Draws boats while race in progress, when leg heading is set. */ private void updateBoats() { - int currentColour = 0; if (boats != null) { + if (boatColours.size() < boats.size()) mapBoatColours(); for (Boat boat : boats) { boolean finished = boat.getCurrentLeg().getName().equals("Finish") || boat.getCurrentLeg().getName().equals("DNF"); boolean isStart = boat.isStarted(); + int sourceID = boat.getSourceID(); if (!finished && isStart) { - displayBoat(boat, boat.getHeading(), colours.get(currentColour)); + displayBoat(boat, boat.getHeading(), boatColours.get(sourceID)); GraphCoordinate wakeFrom = this.map.convertGPS(boat.getCurrentPosition()); GraphCoordinate wakeTo = this.map.convertGPS(boat.getWake()); - displayLine(wakeFrom, wakeTo, colours.get(currentColour)); + displayLine(wakeFrom, wakeTo, boatColours.get(sourceID)); } else if (!isStart) { - displayBoat(boat, boat.getHeading(), colours.get(currentColour)); + displayBoat(boat, boat.getHeading(), boatColours.get(sourceID)); } else { - displayBoat(boat, 0, colours.get(currentColour)); + displayBoat(boat, 0, boatColours.get(sourceID)); } if (raceAnno) displayText(boat.toString(), boat.getAbbrev(), boat.getVelocity(), this.map.convertGPS(boat.getCurrentPosition())); //TODO this needs to be fixed. - drawTrack(boat, colours.get(currentColour)); - currentColour = (currentColour + 1) % colours.size(); + drawTrack(boat, boatColours.get(sourceID)); } } } @@ -330,4 +330,14 @@ public class ResizableRaceCanvas extends ResizableCanvas { )); } + private void mapBoatColours() { + int currentColour = 0; + for (Boat boat : boats) { + if (!boatColours.containsKey(boat.getSourceID())) { + boatColours.put(boat.getSourceID(), colours.get(currentColour)); + } + currentColour = (currentColour + 1) % colours.size(); + } + } + } \ No newline at end of file diff --git a/visualiser/src/main/resources/scenes/race.fxml b/visualiser/src/main/resources/scenes/race.fxml index 7ab63046..39828525 100644 --- a/visualiser/src/main/resources/scenes/race.fxml +++ b/visualiser/src/main/resources/scenes/race.fxml @@ -1,19 +1,22 @@ + + + + - + + - + - + @@ -24,28 +27,13 @@ - - - - - -