Java Arena
Step 3 of 8

Several series from a map

One chart can hold several series. Each gets a line of its own, in a color of its own, and the legend says which line is which: that's what makes lines easy to compare. A bakery counts what it sells of three products each week, one column per product:

The file bakery.csv
week,bread,buns,pies
1,120,80,15
2,115,95,-
3,130,90,18
4,125,-,22
5,140,105,20

The names of the series come from the header, so the program needs no change when the bakery adds a fourth product:

import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.ArrayList;
import java.util.List;
import javafx.application.Application;
import javafx.scene.Scene;
import javafx.scene.chart.LineChart;
import javafx.scene.chart.NumberAxis;
import javafx.scene.chart.XYChart;
import javafx.stage.Stage;

public class Main extends Application {

    @Override
    public void start(Stage stage) throws Exception {
        NumberAxis weeks = new NumberAxis("Week", 1, 5, 1);
        NumberAxis sold = new NumberAxis();
        sold.setLabel("Sold");
        LineChart<Number, Number> chart = new LineChart<>(weeks, sold);
        chart.setTitle("Bakery sales");

        List<String> lines = Files.readAllLines(Paths.get("bakery.csv"));
        String[] names = lines.get(0).split(",");
        List<XYChart.Series<Number, Number>> products = new ArrayList<>();
        for (int c = 1; c < names.length; c++) {
            XYChart.Series<Number, Number> series = new XYChart.Series<>();
            series.setName(names[c]);
            products.add(series);
        }
        for (int i = 1; i < lines.size(); i++) {
            String[] parts = lines.get(i).split(",");
            int week = Integer.valueOf(parts[0]);
            for (int c = 1; c < parts.length; c++) {
                if (!parts[c].equals("-")) {
                    products.get(c - 1).getData().add(new XYChart.Data<>(week, Integer.valueOf(parts[c])));
                }
            }
        }
        chart.getData().addAll(products);

        stage.setScene(new Scene(chart));
        stage.setTitle("Bakery");
        stage.show();
    }

    public static void main(String[] args) {
        launch(Main.class);
    }
}
The window

This window is wider than the screen: scroll it sideways, or read it as text below.

The window as text
Window "Bakery"
  LineChart "Bakery sales"
    x axis NumberAxis "Week" 1 to 5 by 1
    y axis NumberAxis "Sold"
    series "bread": (1, 120) (2, 115) (3, 130) (4, 125) (5, 140)
    series "buns": (1, 80) (2, 95) (3, 90) (5, 105)
    series "pies": (1, 15) (3, 18) (4, 22) (5, 20)
  • Column c of the file is the series at index c - 1 of the list, since column 0 is the week.
  • A - leaves that week out of that series only: the other products of the week still get their points.
  • chart.getData().addAll(products) adds every series of the list, in its order. That order is the order of the legend, and the order in which the chart hands out its colors.
  • The legend is visible, as charts have it by default. With three lines, it's needed.

One series per key

A file can also have one row per measurement. A school garden measures three plants each week, and writes a row for each plant:

The file garden.csv
week,plant,cm
1,tomato,12
1,bean,8
1,pea,5
2,tomato,15
2,bean,13
2,pea,-
3,tomato,19
3,bean,20
3,pea,9

Now the names are in the rows, and the program doesn't know them before it reads the file. A map from each name to its series collects the points: the first row of a plant makes its series, and every row adds a point to the series of its plant:

import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import javafx.application.Application;
import javafx.scene.Scene;
import javafx.scene.chart.LineChart;
import javafx.scene.chart.NumberAxis;
import javafx.scene.chart.XYChart;
import javafx.stage.Stage;

public class Main extends Application {

    @Override
    public void start(Stage stage) throws Exception {
        NumberAxis weeks = new NumberAxis("Week", 1, 3, 1);
        NumberAxis height = new NumberAxis();
        height.setLabel("Height (cm)");
        LineChart<Number, Number> chart = new LineChart<>(weeks, height);
        chart.setTitle("School garden");

        Map<String, XYChart.Series<Number, Number>> byPlant = new HashMap<>();
        List<String> lines = Files.readAllLines(Paths.get("garden.csv"));
        for (int i = 1; i < lines.size(); i++) {
            String[] parts = lines.get(i).split(",");
            if (!parts[2].equals("-")) {
                String plant = parts[1];
                XYChart.Series<Number, Number> series = byPlant.get(plant);
                if (series == null) {
                    series = new XYChart.Series<>();
                    series.setName(plant);
                    byPlant.put(plant, series);
                }
                series.getData().add(new XYChart.Data<>(Integer.valueOf(parts[0]), Integer.valueOf(parts[2])));
            }
        }

        List<String> plants = new ArrayList<>(byPlant.keySet());
        Collections.sort(plants);
        for (String plant : plants) {
            chart.getData().add(byPlant.get(plant));
        }

        stage.setScene(new Scene(chart));
        stage.setTitle("Garden");
        stage.show();
    }

    public static void main(String[] args) {
        launch(Main.class);
    }
}
The window

This window is wider than the screen: scroll it sideways, or read it as text below.

The window as text
Window "Garden"
  LineChart "School garden"
    x axis NumberAxis "Week" 1 to 3 by 1
    y axis NumberAxis "Height (cm)"
    series "bean": (1, 8) (2, 13) (3, 20)
    series "pea": (1, 5) (3, 9)
    series "tomato": (1, 12) (2, 15) (3, 19)
  • The map's values are series. byPlant.get(plant) returns null for a plant that has no series yet, and then the program makes one, names it and puts it in the map. Module 33 gave each new key its own list the same way ("A list as a value").
  • The series go to the chart in the sorted order of their names. A HashMap's order isn't one you choose (module 31), so a program that added them in the map's order would have a legend in an order nobody decided, and the checks compare the series in order. Sorted names give a fixed order: bean, pea, tomato.
  • Each series gets its points in the order of the rows, and the file goes week by week, so every series goes from the smallest x up. Keep it that way: the window as text lists the points in the order they were added, and so do the checks. If a file isn't in order, sort the rows first.

Your task

Challenge 1 of 3

A running club writes down how far each runner ran in each week's training run, one row per runner and week, with a - when a runner skipped the run:

week,runner,km
1,Mia,5.5
1,Leo,7
1,Ava,4
2,Mia,6
2,Leo,-
2,Ava,4.5
3,Mia,6.5
3,Leo,8
3,Ava,5

The window, its line chart and the axes are ready. Read runs.csv and add a series for each runner, named after the runner, with a point (week, km) for each of the runner's rows that has a number, in the order of the file. Read the km as doubles. Add the series to the chart in the alphabetical order of the names (here Ava, Leo, Mia). A runner whose every row has a - gets no series. The task card shows the window for the file above.

Challenge 2

A park measures its young trees in centimeters. What does this program print?

Challenge 3

A town has three swimming pools, and counts each pool's visitors per week in the tab-separated file pools.tsv, one column per pool. A pool that was closed for a week has a -:

week	North	Lake	Park
1	310	520	-
2	295	610	140
3	-	700	180
4	350	655	210

Write start so that the window, titled Pools, shows a line chart (the scene's root) with the title Swimming pool visitors, an auto-ranging x axis labeled Week, an auto-ranging y axis labeled Visitors and a visible legend. It has a series for each pool, named as in the header and in the header's order, with a point (week, visitors) for each week the pool was open, in the order of the file. The file may have any number of pools. The task card shows the window for the file above.