diff --git a/src/main/java/seng302/App.java b/src/main/java/seng302/App.java index 20c01788..64cf5e2c 100644 --- a/src/main/java/seng302/App.java +++ b/src/main/java/seng302/App.java @@ -65,8 +65,8 @@ public class App extends Application { //Change the StreamReceiver in this else block to change the default data source. else{ // sr = new StreamReceiver("localhost", 4949, "RaceStream"); - sr = new StreamReceiver("livedata.americascup.com", 4941, "RaceStream"); // sr = new StreamReceiver("csse-s302staff.canterbury.ac.nz", 4941, "RaceStream"); + sr = new StreamReceiver("livedata.americascup.com", 4941, "RaceStream"); } sr.start(); diff --git a/src/main/java/seng302/controllers/CanvasController.java b/src/main/java/seng302/controllers/CanvasController.java index 647a2488..25530b60 100644 --- a/src/main/java/seng302/controllers/CanvasController.java +++ b/src/main/java/seng302/controllers/CanvasController.java @@ -142,6 +142,7 @@ public class CanvasController { updateFPMCounter = 0; drawFps(frameRate.intValue()); } + raceViewController.updateSparkLine(); } // TODO: 1/05/17 cir27 - Make the RaceObjects update on the actual delay. diff --git a/src/main/java/seng302/controllers/RaceViewController.java b/src/main/java/seng302/controllers/RaceViewController.java index b3a01fa6..03b51cfa 100644 --- a/src/main/java/seng302/controllers/RaceViewController.java +++ b/src/main/java/seng302/controllers/RaceViewController.java @@ -6,7 +6,12 @@ import javafx.collections.FXCollections; import javafx.collections.ObservableList; import javafx.fxml.FXML; import javafx.fxml.FXMLLoader; +import javafx.geometry.Side; import javafx.scene.Scene; +import javafx.scene.chart.LineChart; +import javafx.scene.chart.NumberAxis; +import javafx.scene.chart.XYChart; +import javafx.scene.chart.XYChart.Series; import javafx.scene.control.Button; import javafx.scene.control.CheckBox; import javafx.scene.control.ComboBox; @@ -31,12 +36,18 @@ import seng302.models.stream.StreamParser; import java.io.IOException; import java.util.*; import seng302.models.stream.XMLParser.RaceXMLObject.Participant; +import java.util.stream.Collectors; /** + * * Created by ptg19 on 29/03/17. */ public class RaceViewController extends Thread implements ImportantAnnotationDelegate { + @FXML + private LineChart raceSparkLine; + @FXML + private NumberAxis sparklineYAxis; @FXML private VBox positionVbox; @FXML @@ -60,7 +71,8 @@ public class RaceViewController extends Thread implements ImportantAnnotationDel private boolean displayFps; private Timeline timerTimeline; private Stage stage; - + private static HashMap> sparkLineData = new HashMap<>(); + private static ArrayList racingBoats = new ArrayList<>(); private ImportantAnnotationsState importantAnnotations; private Yacht selectedBoat; @@ -68,6 +80,14 @@ public class RaceViewController extends Thread implements ImportantAnnotationDel // Load a default important annotation state importantAnnotations = new ImportantAnnotationsState(); + //Formatting the y axis of the sparkline + raceSparkLine.getYAxis().setRotate(180); + raceSparkLine.getYAxis().setTickLabelRotation(180); + raceSparkLine.getYAxis().setTranslateX(15); + raceSparkLine.getYAxis().setAutoRanging(false); + + startingBoats = new ArrayList<>(StreamParser.getBoats().values()); + includedCanvasController.setup(this); includedCanvasController.initializeCanvas(); initializeUpdateTimer(); @@ -75,12 +95,10 @@ public class RaceViewController extends Thread implements ImportantAnnotationDel initialiseAnnotationSlider(); initialiseBoatSelectionComboBox(); includedCanvasController.timer.start(); - - selectAnnotationBtn.setOnAction(event -> { - loadSelectAnnotationView(); - }); + selectAnnotationBtn.setOnAction(event -> loadSelectAnnotationView()); } + /** * The important annotations have been changed, update this view * @@ -91,6 +109,7 @@ public class RaceViewController extends Thread implements ImportantAnnotationDel setAnnotations((int) annotationSlider.getValue()); // Refresh the displayed annotations } + /** * Loads the "select annotations" view in a new window */ @@ -128,6 +147,7 @@ public class RaceViewController extends Thread implements ImportantAnnotationDel (observable, oldValue, newValue) -> displayFps = !displayFps); } + private void initialiseAnnotationSlider() { annotationSlider.setLabelFormatter(new StringConverter() { @Override @@ -168,6 +188,79 @@ public class RaceViewController extends Thread implements ImportantAnnotationDel } + /** + * Used to add any new boats into the race that may have started late or not have had data received yet + */ + void updateSparkLine(){ + // Collect the racing boats that aren't already in the chart + ArrayList sparkLineCandidates = startingBoats.stream().filter(yacht -> !sparkLineData.containsKey(yacht.getSourceID()) + && yacht.getPosition() != null & yacht.getPosition() != "-").collect(Collectors.toCollection(ArrayList::new)); + + // Obtain the qualifying boats to set the max on the Y axis + racingBoats = startingBoats.stream().filter(yacht -> + yacht.getPosition() != null & yacht.getPosition() != "-").collect(Collectors.toCollection(ArrayList::new)); + sparklineYAxis.setUpperBound(racingBoats.size() + 1); + + // Create a new data series for new boats + sparkLineCandidates.stream().filter(yacht -> yacht.getPosition() != null).forEach(yacht -> { + Series yachtData = new Series<>(); + yachtData.setName(yacht.getBoatName()); + yachtData.getData().add(new XYChart.Data<>(Integer.toString(yacht.getLegNumber()), 1 + racingBoats.size() - Double.parseDouble(yacht.getPosition()))); + sparkLineData.put(yacht.getSourceID(), yachtData); + }); + + // Lambda function to sort the series in order of leg (later legs shown more to the right) + List> positions = new ArrayList<>(sparkLineData.values()); + Collections.sort(positions, (o1, o2) -> { + Integer leg1 = Integer.parseInt(o1.getData().get(o1.getData().size()-1).getXValue()); + Integer leg2 = Integer.parseInt(o2.getData().get(o2.getData().size()-1).getXValue()); + if (leg2 < leg1){ + return 1; + } else { + return -1; + } + }); + + + // Adds the new data series to the sparkline (and set the colour of the series) + raceSparkLine.setCreateSymbols(false); + positions.stream().filter(spark -> !raceSparkLine.getData().contains(spark)).forEach(spark -> { + raceSparkLine.getData().add(spark); + spark.getNode().lookup(".chart-series-line").setStyle("-fx-stroke:" + getBoatColorAsRGB(spark.getName())); + }); + } + + + /** + * Updates the yachts sparkline of the desired boat and using the new leg number + * @param yacht The yacht to be updated on the sparkline + * @param legNumber the leg number that the position will be assigned to + */ + public static void updateYachtPositionSparkline(Yacht yacht, Integer legNumber){ + XYChart.Series positionData = sparkLineData.get(yacht.getSourceID()); + positionData.getData().add(new XYChart.Data<>(Integer.toString(legNumber), 1 + racingBoats.size() - Double.parseDouble(yacht.getPosition()))); + } + + + /** + * gets the rgb string of the boats colour to use for the chart via css + * @param boatName boat passed in to get the boats colour + * @return the colour as an rgb string + */ + private String getBoatColorAsRGB(String boatName){ + Color color = Color.WHITE; + for (Yacht yacht: startingBoats){ + if (Objects.equals(yacht.getBoatName(), boatName)){ + color = yacht.getColour(); + } + } + return String.format( "#%02X%02X%02X", + (int)( color.getRed() * 255 ), + (int)( color.getGreen() * 255 ), + (int)( color.getBlue() * 255 ) ); + } + + /** * Initalises a timer which updates elements of the RaceView such as wind direction, boat * orderings etc.. which are dependent on the info from the stream parser constantly. @@ -184,7 +277,6 @@ public class RaceViewController extends Thread implements ImportantAnnotationDel updateWindDirection(); updateOrder(); updateBoatSelectionComboBox(); - }) ); @@ -345,7 +437,7 @@ public class RaceViewController extends Thread implements ImportantAnnotationDel } - public boolean isDisplayFps() { + boolean isDisplayFps() { return displayFps; } @@ -451,4 +543,13 @@ public class RaceViewController extends Thread implements ImportantAnnotationDel Stage getStage() { return stage; } + + /** + * Used for when the boat attempts to add data to the sparkline (first checks if the sparkline contains info on it) + * @param yachtId + * @return + */ + public static boolean sparkLineStatus(Integer yachtId) { + return sparkLineData.containsKey(yachtId); + } } \ No newline at end of file diff --git a/src/main/java/seng302/models/Yacht.java b/src/main/java/seng302/models/Yacht.java index 9644e5f1..c5bdcbb7 100644 --- a/src/main/java/seng302/models/Yacht.java +++ b/src/main/java/seng302/models/Yacht.java @@ -1,6 +1,8 @@ package seng302.models; + import javafx.scene.paint.Color; +import seng302.controllers.RaceViewController; import java.text.DateFormat; import java.text.SimpleDateFormat; @@ -34,6 +36,7 @@ public class Yacht { // Mark rounding private Long markRoundingTime; + /** * Used in EventTest and RaceTest. * @@ -105,6 +108,9 @@ public class Yacht { } public void setLegNumber(Integer legNumber) { + if (colour != null && position != "-" && legNumber != this.legNumber&& RaceViewController.sparkLineStatus(sourceID)) { + RaceViewController.updateYachtPositionSparkline(this, legNumber); + } this.legNumber = legNumber; } diff --git a/src/main/resources/css/master.css b/src/main/resources/css/master.css index 8223fc00..bef93923 100644 --- a/src/main/resources/css/master.css +++ b/src/main/resources/css/master.css @@ -181,4 +181,8 @@ Remove scroll bars -fx-background-radius: 0; -fx-background-insets: 0; -fx-padding: 0; -} \ No newline at end of file +} + +.chart{ + -fx-background-color: #ffffff; +} diff --git a/src/main/resources/views/RaceView.fxml b/src/main/resources/views/RaceView.fxml index f43099a1..b225ec98 100644 --- a/src/main/resources/views/RaceView.fxml +++ b/src/main/resources/views/RaceView.fxml @@ -1,6 +1,7 @@ + @@ -50,6 +51,14 @@