Saving data from table of COVID-19 infections in Germany

Posted on

Problem

I wrote a program that saves the data from the first table of this Wikipedia article (in German) and then saves the column “Gesamt” (contains daily update of absolute numbers of COVID-19 infected people in Germany) together with the date in a CSV file. After that it draws a chart with logarithmical axis.

For the parsing, I used JSoup and for saving the data in the CSV file, I used a FileWriter. For the chart, I used swing and JFree.

import java.awt.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.time.LocalDate;
import java.io.FileWriter;
import java.time.format.DateTimeFormatter;
import java.io.IOException;

import org.jfree.chart.axis.LogAxis;
import org.jfree.chart.axis.NumberAxis;
import org.jfree.chart.plot.XYPlot;
import org.jfree.chart.renderer.xy.XYLineAndShapeRenderer;
import org.jsoup.Jsoup;
import org.jsoup.nodes.Document;
import org.jsoup.nodes.Element;
import org.jsoup.select.Elements;

import javax.swing.*;

import org.jfree.chart.ChartPanel;
import org.jfree.chart.JFreeChart;
import org.jfree.data.xy.XYSeries;
import org.jfree.data.xy.XYSeriesCollection;

public class Corona {

    public static void main(String[] args) throws IOException {
        int[] array = getData();
        saveData(array);
        visualizeData(array);
    }

    public static int[] getData() {

        //Saving data from wikipedia in a 2D-String-Array

        String url = "https://de.wikipedia.org/wiki/COVID-19-Pandemie_in_Deutschland";
        String[][] a = new String[365][30];
        try{
            Document doc = Jsoup.connect(url).get();
            Element table = doc.getElementsByClass("wikitable zebra toptextcells mw-collapsible").first();
            Elements rows = table.getElementsByTag("tr");

            int i = 0;
            int j = 0;
            for (Element row : rows) {
                Elements cells = row.getElementsByTag("td");
                for (Element cell : cells) {
                    a[j][i] = cell.text().concat(", ");
                    i++;
                }
                j++;
                i = 0;
            }

        }

        catch (IOException e){
            e.printStackTrace();
        }

        //Only taking column with absolute numbers for whole country

        int[] array = new int[365];
        for(int k = 0; k < array.length; k++) {
            if(a[k][17] != null) {
                a[k][17] = a[k][17].split(",")[0];
                a[k][17] = a[k][17].replaceAll("[.]","");
                array[k] = Integer.parseInt(a[k][17]);
            }

        }
        return array;
    }

    public static void saveData(int[] array) throws IOException {

        //Writing date and data from array to csv-file

        LocalDate date = LocalDate.of(2020, 2, 24);
        FileWriter writer = new FileWriter("data.csv");
        writer.append("");
        writer.append("n");
        for(int i = 0; i < array.length - 1; i++) {
            writer.append(date.format(DateTimeFormatter.ofPattern("dd/MM/yyyy")));
            writer.append(",");
            writer.append(Integer.toString(array[i + 1]));
            writer.append("n");
            date = date.plusDays(1);
        }
        writer.flush();
        writer.close();
    }

    public static void visualizeData(int[] array) {
        SwingUtilities.invokeLater(new Runnable() {
            @Override
            public void run() {
                //JFrame and JPanel

                JFrame frame = new JFrame("Corona-Statistics");
                frame.setSize(600,600);
                frame.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
                JPanel panel = new JPanel(new BorderLayout());

                //Series gets filled up with data
                XYSeries series = new XYSeries("XYGraph");

                int i = 1;
                while(array[i] > 0) {
                    series.add(i - 1, array[i]);
                    i++;
                }

                //Printing the chart

                NumberAxis xAxis = new NumberAxis("Days since 24-02-20");
                LogAxis yAxis = new LogAxis("People infected with Covid-19 in Germany");
                yAxis.setBase(10);
                XYPlot plot = new XYPlot(new XYSeriesCollection(series),
                        xAxis, yAxis, new XYLineAndShapeRenderer(true, false));
                JFreeChart chart = new JFreeChart(
                        "People infected with Covid-19 in Germany", JFreeChart.DEFAULT_TITLE_FONT, plot, false);
                frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
                ChartPanel chartpanel = new ChartPanel(chart);

                //Adding chartpanel to panel

                panel.add(chartpanel, BorderLayout.NORTH);

                //Button to open csv

                JButton button = new JButton("Open csv");
                button.addActionListener(new ActionListener() {
                    @Override
                    public void actionPerformed(ActionEvent actionEvent) {
                        Runtime run = Runtime.getRuntime();
                        try {
                            Process pp = run.exec("libreoffice data.csv");
                        }
                        catch(Exception e) {
                            e.printStackTrace();
                        }
                    }
                });

                //Button to update data

                JButton button2 = new JButton("Update data");
                button2.addActionListener(new ActionListener() {
                    @Override
                    public void actionPerformed(ActionEvent actionEvent) {
                        try {
                            frame.setVisible(false);
                            frame.dispose();
                            final int[] a = getData();
                            saveData(a);
                            visualizeData(a);

                        }
                        catch(IOException e) {

                        }
                    }
                });

                //Adding buttons to subpanel

                JPanel subpanel = new JPanel(new GridLayout(1,2));
                subpanel.add(button);
                subpanel.add(button2);

                panel.add(subpanel, BorderLayout.SOUTH);
                frame.add(panel);
                frame.setVisible(true);
            }
        });

    }
}

The program has no special purpose, it’s for practice only.

My question now is: How can I improve the code?

Solution

I have some suggestions for your code:

String[][] a = new String[365][30];
int i = 0;
int j = 0;
for (Element row : rows) {
    Elements cells = row.getElementsByTag("td");
    for (Element cell : cells) {
        a[j][i] = cell.text().concat(", ");
        i++;
    }
    j++;
    i = 0;
}

You are storing a m * n matrix of values retrieved by jsoup in a 2d array, but this can be avoided? Here the second part of your code :

int[] array = new int[365];
for(int k = 0; k < array.length; k++) {
    if(a[k][17] != null) {
        a[k][17] = a[k][17].split(",")[0];
        a[k][17] = a[k][17].replaceAll("[.]","");
        array[k] = Integer.parseInt(a[k][17]);
    }
}

The values you are interested are in the 17th column of the table, so instead of a 2d matrix, you can use directly an int array where to store elements. You are doing a lot of not necessary work parsing by yourself strings like “1.234” and convert them to integer without the point for thousands, use instead the class NumberFormat:

NumberFormat nf = NumberFormat.getNumberInstance(Locale.GERMAN);
int value = nf.parse("1.234").intValue(); // it will contain the number 1234

You can extract the 17th column values from your table using the jsoup Element get method checking if the row you are examining has at least 18 elements:

for (int i = 0; i < rows.size(); ++i) {
    Elements cells = rows.get(i).getElementsByTag("td");
    if (cells.size() > 17) {
        Element cell = cells.get(17);
        array[i] = nf.parse(cell.text()).intValue(); //<-- nf.parse call
    }
}

So your method getData can be rewritten like this:

public static int[] getData() throws IOException, ParseException {
    String url = "https://de.wikipedia.org/wiki/COVID-19-Pandemie_in_Deutschland";
    int[] array = new int[365];

    Document doc = Jsoup.connect(url).get();
    Element table = doc.getElementsByClass("wikitable zebra toptextcells mw-collapsible").first();
    Elements rows = table.getElementsByTag("tr");

    NumberFormat nf = NumberFormat.getNumberInstance(Locale.GERMAN);

    for (int i = 0; i < rows.size(); ++i) {
        Elements cells = rows.get(i).getElementsByTag("td");
        if (cells.size() > 17) {
            Element cell = cells.get(17);
            array[i] = nf.parse(cell.text()).intValue();
        }
    }

    return array;
}

Some minor changes can be applied to your other method saveData, first is use of try with resources to avoid manual closing and flushing of the FileWriter:

try (FileWriter writer = new FileWriter("data.csv")) { /*here your logic*/ }

Second, to avoid use of consecutive appends , you can use the write method of the FileWriter class rewriting the method in this way:

public static void saveData(int[] array) throws IOException {
    LocalDate date = LocalDate.of(2020, 2, 24);
    DateTimeFormatter df = DateTimeFormatter.ofPattern("dd/MM/yyyy");

    try (FileWriter writer = new FileWriter("data.csv")) {
        writer.write("n");
        String template = "%s,%dn";
        for(int i = 0; i < array.length - 1; i++) {
            String data = String.format(template, date.format(df), array[i + 1]);
            writer.write(data);
            date = date.plusDays(1);
        }
    }
}

I defined a template variable so the format of the data is more readable and easier to modify.

Leave a Reply

Your email address will not be published. Required fields are marked *