package visualiser.model; import javafx.application.Platform; import javafx.collections.ObservableList; 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 * track of {@link VisualiserBoat}s in a race and their current * placing position as they complete each {@link shared.model.Leg} by * passing a course {@link shared.model.Mark}.
* This sparkline is displayed using the * {@link visualiser.Controllers.RaceController}. */ public class Sparkline { /** * The race to observe. */ private VisualiserRace race; /** * The boats to observe. */ private ObservableList boats; /** * The number of legs in the race. * Used to correctly scale the linechart. */ private Integer legNum; /** * The linchart to plot sparklines on. */ private LineChart sparklineChart; /** * The x axis of the sparkline chart. */ private NumberAxis xAxis; /** * The y axis of the sparkline chart. */ private NumberAxis yAxis; /** * Constructor to set up initial sparkline (LineChart) object * @param race The race to listen to. * @param sparklineChart JavaFX LineChart for the sparkline. */ public Sparkline(VisualiserRace race, LineChart sparklineChart) { this.race = race; this.boats = race.getBoats(); this.legNum = race.getLegCount(); this.sparklineChart = sparklineChart; this.yAxis = (NumberAxis) sparklineChart.getYAxis(); this.xAxis = (NumberAxis) sparklineChart.getXAxis(); createSparkline(); } /** * 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. */ 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 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 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 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()) + ";"); } sparklineChart.setCreateSymbols(false); //Set x axis details xAxis.setAutoRanging(false); xAxis.setTickMarkVisible(false); xAxis.setTickLabelsVisible(false); xAxis.setMinorTickVisible(false); xAxis.setLowerBound(0); xAxis.setUpperBound(legNum + 2); xAxis.setTickUnit(1); //Set y axis details yAxis.setLowerBound(boats.size()); yAxis.setUpperBound(1); yAxis.setAutoRanging(false); yAxis.setLabel("Position in Race"); yAxis.setTickUnit(-1);//Negative tick reverses the y axis. yAxis.setTickMarkVisible(true); yAxis.setTickLabelsVisible(true); yAxis.setTickMarkVisible(true); yAxis.setMinorTickVisible(true); } /** * Converts a color to a hex string, starting with a {@literal #} symbol. * @param color The color to convert. * @return Hex string of the color (e.g., {@literal "#11AB4C"}). */ private static String colourToHex(Color color) { return String.format( "#%02X%02X%02X", (int)( color.getRed() * 255 ), (int)( color.getGreen() * 255 ), (int)( color.getBlue() * 255 ) ); } }