javafxlinechartobservablelist

How to create an observable list of XYChart.Series that combines duplicate entry


I want to populate a line chart with data from the database. To achieve this, I created a class that returns an ObservableList<XYChart.Series>. But I struggle to merge the same XYChart.Series name (Like the example below).

MVCE

import javafx.application.Application;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.scene.Scene;
import javafx.scene.chart.CategoryAxis;
import javafx.scene.chart.LineChart;
import javafx.scene.chart.NumberAxis;
import javafx.scene.chart.XYChart;
import javafx.stage.Stage;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;

public class Sample extends Application {
    @Override public void start(Stage stage) {
        //create the chart
        final CategoryAxis xAxis = new CategoryAxis();
        final NumberAxis yAxis = new NumberAxis();
        xAxis.setLabel("Year");

        final LineChart<String,Number> lineChart =
                new LineChart<>(xAxis, yAxis);

        lineChart.setTitle("Employment Monitoring, 2020");

        for(XYChart.Series series : getData()){
            lineChart.getData().add(series);
        }

        // show the scene.
        Scene scene = new Scene(lineChart, 800, 600);
        stage.setScene(scene);
        stage.show();
    }

    /* How can I return the right value to the line chart ? */
    private ObservableList<XYChart.Series> getData(){
        var list = FXCollections.<XYChart.Series>observableArrayList();
        
        // Supposed that this data where values retrieved from the database
        ArrayList<List> arrayList = new ArrayList<>();
        arrayList.add(Arrays.asList("Permanent", "2011", 5));
        arrayList.add(Arrays.asList("Job Order", "2011", 16));
        arrayList.add(Arrays.asList("Permanent", "2012", 10));
        arrayList.add(Arrays.asList("Job Order", "2012", 19));

        for (List obs : arrayList){
            list.add(
                    new XYChart.Series(
                            (String) obs.get(0),
                            FXCollections.observableArrayList(
                                    new XYChart.Data<>((String) obs.get(1), (Number) obs.get(2))
                            )
                    ));
        }
        return list;
    }

    public static void main(String[] args) { launch(args); }
}

This will produce this output output on the first

As you have noticed, there are duplicate series for Permanent and Job Order


Question is

How will I merge that duplicate entry so that I can achieve the output below?

without using a model class

expected ouput


EDIT

As @kleopatra said, (Based from my current knowledge on java) I tried to filter the data from the list by :

for (XYChart.Series series : getData()){
    XYChart.Data item = (XYChart.Data) series.getData().get(0);
    
    if (lineChart.getData().size() > 0){
        for (XYChart.Series duplicate : lineChart.getData()) 
        {
            if (duplicate.getName().equals(series.getName()))
            {
                duplicate.getData().add(item);
            } else {
               // lineChart.getData().add(series);
            }
        }
    } else {
        lineChart.getData().add(series);
    }
}

instead of just :

for(XYChart.Series series : getData())
{
    lineChart.getData().add(series);
}

though it gives me the concatenated output for the Permanent series (which is what I want to achieve). I can hardly add another series e.g. Job Order to the line chart. As when I uncomment the code under else condition. I got an error.

ConcurrentModificationException


Solution

  • Using @kleopatra's ideas, you can filter the list and create the Series using the filtered data. Your requirements complicate things. Arrays.asList("Permanent", "2011", 5) also complicates things. It's bad practice in Java to create a List of different types. In my example, I used a HashMap to filter the data.

    import java.util.ArrayList;
    import java.util.Arrays;
    import java.util.HashMap;
    import java.util.List;
    import java.util.Map;
    import javafx.application.Application;
    import javafx.scene.Scene;
    import javafx.scene.chart.CategoryAxis;
    import javafx.scene.chart.LineChart;
    import javafx.scene.chart.NumberAxis;
    import javafx.scene.chart.XYChart;
    import javafx.stage.Stage;
    
    public class App extends Application
    {
    
        @Override
        public void start(Stage stage)
        {
            //create the chart
            final CategoryAxis xAxis = new CategoryAxis();
            final NumberAxis yAxis = new NumberAxis();
            xAxis.setLabel("Year");
    
            final LineChart<String, Number> lineChart
                    = new LineChart<>(xAxis, yAxis);
    
            lineChart.setTitle("Employment Monitoring, 2020");
            lineChart.getData().addAll(getData());
            
    
            // show the scene.
            Scene scene = new Scene(lineChart, 800, 600);
            stage.setScene(scene);
            stage.show();
        }
    
        /* How can I return the right value to the line chart ? */
        private List<XYChart.Series<String, Number>> getData()
        {
            List<XYChart.Series<String, Number>> series = new ArrayList();
    
            // Supposed that this data where values retrieved from the database
            List<List> items = new ArrayList<>();
            items.add(Arrays.asList("Permanent", "2011", 5));
            items.add(Arrays.asList("Job Order", "2011", 16));
            items.add(Arrays.asList("Permanent", "2012", 10));
            items.add(Arrays.asList("Job Order", "2012", 19));
    
            Map<String, List> map = new HashMap();
            for (int i = 0; i < items.size(); i++) {
                if (map.get(items.get(i).get(0).toString()) == null) {
                    List newEntry = new ArrayList();
                    newEntry.add(items.get(i).get(1));
                    newEntry.add(items.get(i).get(2));
                    map.put(items.get(i).get(0).toString(), newEntry);
                    System.out.println("Createing array " + items.get(i).get(0).toString() + "  Adding " + items.get(i).get(1) + ":" + items.get(i).get(2));
                }
                else {
                    List oldList = map.get(items.get(i).get(0).toString());
                    oldList.add(items.get(i).get(1));
                    oldList.add(items.get(i).get(2));
                    System.out.println("Adding to array " + items.get(i).get(0).toString() + "  Adding " + items.get(i).get(1) + ":" + items.get(i).get(2));
                }
            }
    
            for (Map.Entry<String, List> entry : map.entrySet()) {
                XYChart.Series<String, Number> tempItemsSeries = new XYChart.Series();
                tempItemsSeries.setName(entry.getKey());
    
                //System.out.println(entry.getValue().size() + Arrays.toString(entry.getValue().toArray()));
                for (int i = 0; i < entry.getValue().size(); i = i + 2) {
                    tempItemsSeries.getData().add(new XYChart.Data(entry.getValue().get(i), entry.getValue().get(i + 1)));
                }
                series.add(tempItemsSeries);
            }
    
            return series;
        }
    
        public static void main(String[] args)
        {
            launch(args);
        }
    }
    

    enter image description here