Note that the format you're using (YYYY-MM-DD) has the very nice property that the lexicographical order of the formatted string is identical to the order of the dates. So it's enough to order the strings on the x-axis here. The easiest way to do this I can see is to use a Category
axis, and just use supply a SortedList<Data<String, Number>>
to the series for the chart. Then when you add any data to the underlying list, the data remains correctly ordered in the chart.
Here's a SSCCE. In this example, I only have one value for any given date. The addData(...)
method either creates a new data point (if none exists with that date) or adds the y-value to the existing data point.
import java.time.LocalDate;
import java.time.format.DateTimeFormatter;
import java.util.Random;
import javafx.application.Application;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.collections.transformation.SortedList;
import javafx.geometry.Insets;
import javafx.geometry.Pos;
import javafx.scene.Scene;
import javafx.scene.chart.CategoryAxis;
import javafx.scene.chart.LineChart;
import javafx.scene.chart.NumberAxis;
import javafx.scene.chart.XYChart.Data;
import javafx.scene.chart.XYChart.Series;
import javafx.scene.control.Button;
import javafx.scene.control.DatePicker;
import javafx.scene.control.Spinner;
import javafx.scene.layout.BorderPane;
import javafx.scene.layout.HBox;
import javafx.stage.Stage;
public class LineChartWithDatesAsStrings extends Application {
private DateTimeFormatter formatter = DateTimeFormatter.ISO_LOCAL_DATE ;
@Override
public void start(Stage primaryStage) {
CategoryAxis xAxis = new CategoryAxis();
NumberAxis yAxis = new NumberAxis();
ObservableList<Data<String, Number>> data = FXCollections.observableArrayList() ;
SortedList<Data<String, Number>> sortedData = new SortedList<>(data, (data1, data2) ->
data1.getXValue().compareTo(data2.getXValue()));
LineChart<String, Number> chart = new LineChart<>(xAxis, yAxis);
chart.getData().add(new Series<>(sortedData));
chart.setAnimated(false);
final int dayRange = 60 ;
LocalDate today = LocalDate.now() ;
Random rng = new Random();
for (int i = 0; i < 20 ; i++) {
LocalDate date = today.minusDays(rng.nextInt(dayRange));
String formattedDate = formatter.format(date);
double value = rng.nextDouble() ;
addData(data, formattedDate, value);
}
DatePicker datePicker = new DatePicker();
Spinner<Double> valuePicker = new Spinner<>(0.0, 1.0, 0, 0.1);
valuePicker.setEditable(true);
Button addButton = new Button("Add");
addButton.setOnAction(e -> addData(data, formatter.format(datePicker.getValue()), valuePicker.getValue()));
HBox controls = new HBox(5, datePicker, valuePicker, addButton);
controls.setAlignment(Pos.CENTER);
controls.setPadding(new Insets(5));
BorderPane root = new BorderPane(chart, null, null, controls, null);
primaryStage.setScene(new Scene(root, 600, 600));
primaryStage.show();
}
private void addData(ObservableList<Data<String, Number>> data, String formattedDate, double value) {
Data<String, Number> dataAtDate = data.stream()
.filter(d -> d.getXValue().equals(formattedDate))
.findAny()
.orElseGet(() -> {
Data<String, Number> newData = new Data<String, Number>(formattedDate, 0.0);
data.add(newData);
return newData ;
}) ;
dataAtDate.setYValue(dataAtDate.getYValue().doubleValue() + value);
}
public static void main(String[] args) {
launch(args);
}
}
If you want to use a format without the nice order-preserving property, you just need to parse the strings back to dates in the comparator, and compare the dates. E.g. if you had something like
formatter = DateTimeFormatter.ofLocalizedDate(FormatStyle.MEDIUM) ;
it would work with
SortedList<Data<String, Number>> sortedData = new SortedList<>(data, (data1, data2) -> {
LocalDate date1 = LocalDate.parse(data1.getXValue(), formatter);
LocalDate date2 = LocalDate.parse(data2.getXValue(), formatter);
return date1.compareTo(date2);
});
The "correct" way to do this would be to make the data type a LocalDate
, i.e. you would have a LineChart<LocalDate, Number>
: however defining an Axis<LocalDate>
seems to be way more work than it should be.