5

How to sort my LineChart X Axis by dates ?

Now my LineChart looks like that

LineCHart with unsorted X Axis

I was trying to cut the dates and convert it to int, but now I don't know what to do with this...

    datesToCompare.addAll(LastHoursAndDates.keySet()); // dates in String eg. 2015-12-25
    List<Integer> year = new ArrayList<Integer>(); // list for year after split
    List<Integer> month = new ArrayList<Integer>();// list for month after split
    List<Integer> days = new ArrayList<Integer>(); // list for days after split

    for(int i =0;i < LastHoursAndDates.size();i++)
    {
            sorting = datesToCompare.get(i).split("-");
            year.add(Integer.valueOf(sorting[0]));
            month.add(Integer.valueOf(sorting[1]));
            days.add(Integer.valueOf(sorting[2]));

                for(int j =0;j < LastHoursAndDates.size();j++)
                {
                    if(year.get(i) == year.get(j))
                    {
                        if(month.get(i) == month.get(j))
                        {
                            //???????
                        }
                    }
                }



}//for

Maybe someone had the same problem ?

Paweł Stanecki
  • 454
  • 2
  • 10
  • 24
  • You are probably going about it the wrong way and should use a comparable value for the data in your series (like an [Instant](https://docs.oracle.com/javase/tutorial/datetime/iso/instant.html)) and use a [TickLabelFormatter](https://docs.oracle.com/javase/8/javafx/api/javafx/scene/chart/ValueAxis.html#tickLabelFormatterProperty) to format the dates. Note I haven't actually tried that. – jewelsea Dec 30 '15 at 13:35
  • There may be simpler solution such as using a category axis and just adding date strings sequentially for the categories. See [creating categories for a line chart](https://docs.oracle.com/javafx/2/charts/line-chart.htm). – jewelsea Dec 30 '15 at 13:36
  • Also related: [How to parse/format dates with LocalDateTime? (Java 8)](http://stackoverflow.com/questions/22463062/how-to-parse-format-dates-with-localdatetime-java-8). – jewelsea Dec 30 '15 at 13:46
  • 1
    Or [set the data](http://docs.oracle.com/javase/8/javafx/api/javafx/scene/chart/XYChart.Series.html#setData-javafx.collections.ObservableList-) in the series to a [`SortedList`](http://docs.oracle.com/javase/8/javafx/api/javafx/collections/transformation/SortedList.html) with an appropriate comparator installed? – James_D Dec 30 '15 at 14:31
  • @jewelsea Thank you, but I did not add that, these dates are from the [DatePicker](https://docs.oracle.com/javase/8/javafx/user-interface-tutorial/date-picker.htm). User of my app chose it before. – Paweł Stanecki Dec 30 '15 at 19:00

1 Answers1

3

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.

James_D
  • 201,275
  • 16
  • 291
  • 322