Skip to content

Instantly share code, notes, and snippets.

@james-d
Last active February 11, 2022 04:33
Show Gist options
  • Save james-d/6cf08723062106802a918c2f982c8447 to your computer and use it in GitHub Desktop.
Save james-d/6cf08723062106802a918c2f982c8447 to your computer and use it in GitHub Desktop.
Covid count viewer by county and date in the US. Mostly an experiment to test JavaFX TableView performance.
package org.james_d.examples.covidcases;
import com.opencsv.CSVReader;
import javafx.application.Application;
import javafx.beans.binding.Bindings;
import javafx.beans.property.SimpleIntegerProperty;
import javafx.beans.property.SimpleObjectProperty;
import javafx.beans.property.SimpleStringProperty;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.collections.transformation.FilteredList;
import javafx.concurrent.Task;
import javafx.geometry.Insets;
import javafx.scene.Scene;
import javafx.scene.control.*;
import javafx.scene.layout.BorderPane;
import javafx.scene.layout.HBox;
import javafx.scene.layout.VBox;
import javafx.stage.Stage;
import javafx.util.Callback;
import java.io.InputStreamReader;
import java.net.URL;
import java.time.LocalDate;
import java.time.format.DateTimeFormatter;
import java.time.format.FormatStyle;
import java.util.*;
import java.util.function.Function;
public class CovidCases extends Application {
private final ObservableList<CovidCaseData> itemsList = FXCollections.observableArrayList();
private final FilteredList<CovidCaseData> filteredList = new FilteredList<>(itemsList, c -> true);
private final MenuButton countyFilter = new MenuButton("Select County");
private final DatePicker dateFilter = new DatePicker();
@Override
public void start(Stage stage) {
DataReader reader = new DataReader();
TableView<CovidCaseData> table = new TableView<>();
table.setItems(filteredList);
reader.setOnSucceeded(event -> {
itemsList.setAll(reader.getValue().data());
buildMenu(reader.getValue().counties());
});
reader.setOnFailed(event -> reader.getException().printStackTrace());
Thread thread = new Thread(reader);
thread.setDaemon(true);
thread.start();
TableColumn<CovidCaseData, String> idCol = new TableColumn<>("Id");
idCol.setCellValueFactory(cellData -> new SimpleStringProperty(cellData.getValue().countyUID()));
// Not used
TableColumn<CovidCaseData, String> countyCol = new TableColumn<>("County");
countyCol.setCellValueFactory(cellData -> new SimpleStringProperty(cellData.getValue().county()));
// Not used
TableColumn<CovidCaseData, String> stateCol = new TableColumn<>("State");
stateCol.setCellValueFactory(cellData -> new SimpleStringProperty(cellData.getValue().state()));
TableColumn<CovidCaseData, String> keyCol = new TableColumn<>("Key");
keyCol.setCellValueFactory(cellData -> new SimpleStringProperty(cellData.getValue().key()));
keyCol.setPrefWidth(200);
TableColumn<CovidCaseData, LocalDate> dateCol = new TableColumn<>("Date");
dateCol.setCellValueFactory(cellData -> new SimpleObjectProperty<>(cellData.getValue().date()));
DateTimeFormatter formatter = DateTimeFormatter.ofLocalizedDate(FormatStyle.MEDIUM);
dateCol.setCellFactory(TextTableCell.of(formatter::format));
TableColumn<CovidCaseData, Number> countCol = new TableColumn<>("Count");
countCol.setCellValueFactory(cellData -> new SimpleIntegerProperty(cellData.getValue().count()));
countCol.setCellFactory(TextTableCell.of(count -> String.format("%,d", count.intValue())));
table.getColumns().add(idCol);
table.getColumns().add(keyCol);
table.getColumns().add(dateCol);
table.getColumns().add(countCol);
BorderPane root = new BorderPane(table);
VBox status= new VBox(5);
Label countyCount = new Label();
countyCount.textProperty().bind(reader.messageProperty());
Label itemCount = new Label();
itemCount.textProperty().bind(Bindings.format("Items in list: %,d", Bindings.size(filteredList)));
status.getChildren().addAll(countyCount, itemCount);
root.setBottom(status);
dateFilter.setOnAction(e -> {
if (dateFilter.getValue() != null) {
filteredList.setPredicate(
covidCaseData -> Objects.equals(covidCaseData.date(), dateFilter.getValue()));
}
});
Button reset = new Button("Show all");
reset.setOnAction(e -> {
dateFilter.setValue(null);
filteredList.setPredicate(covidCaseData -> true);
});
HBox filters = new HBox(10, countyFilter, dateFilter, reset);
filters.setPadding(new Insets(10));
root.setTop(filters);
Scene scene = new Scene(root);
stage.setScene(scene);
stage.show();
}
private void buildMenu(Map<String, List<String>> counties) {
counties.keySet().forEach(state -> {
Menu stateMenu = new Menu(state);
counties.get(state).forEach(county -> {
MenuItem item = new MenuItem(county);
item.setOnAction(event ->{
dateFilter.setValue(null);
filteredList.setPredicate(covidCaseData ->
covidCaseData.county().equals(county)
&& covidCaseData.state().equals(state));
});
stateMenu.getItems().add(item);
});
countyFilter.getItems().add(stateMenu);
});
}
private static class TextTableCell<S,T> extends TableCell<S,T> {
private final Function<T, String> format ;
TextTableCell(Function<T, String> format) {
this.format = format ;
}
@Override
protected void updateItem(T item, boolean empty) {
super.updateItem(item, empty);
if (empty || item == null) setText("");
else setText(format.apply(item));
}
static <S,T> Callback<TableColumn<S,T>, TableCell<S,T>> of(Function<T, String> format) {
return tc -> new TextTableCell<>(format);
}
}
private record CovidCaseData(String countyUID, String county, String state,
String key, LocalDate date, int count) { }
private record LoadResults(List<CovidCaseData> data, Map<String, List<String>> counties) {}
private static class DataReader extends Task<LoadResults> {
@Override
protected LoadResults call() throws Exception {
String url = "https://raw.githubusercontent.com/CSSEGISandData/COVID-19/master/" +
"csse_covid_19_data/csse_covid_19_time_series/time_series_covid19_confirmed_US.csv";
try (
CSVReader parser = new CSVReader(
new InputStreamReader(new URL(url).openStream())
)
) {
List<CovidCaseData> data = new ArrayList<>();
String[] header = parser.readNext();
DateTimeFormatter formatter = DateTimeFormatter.ofPattern("M/d/yy");
List<LocalDate> dateList = new ArrayList<>();
for (int i = 11; i < header.length ; i++) {
dateList.add(LocalDate.parse(header[i], formatter));
}
Map<String, List<String>> counties = new TreeMap<>();
int count = 0 ;
for (String[] tokens : parser) {
String countyUID = tokens[0];
String county = tokens[5];
String state = tokens[6];
String key = tokens[10];
for (int i = 11 ; i < tokens.length ; i++) {
data.add(new CovidCaseData(countyUID, county, state, key,
dateList.get(i-11), Integer.parseInt(tokens[i])));
}
counties.computeIfAbsent(state, s -> new ArrayList<>()).add(county);
count++;
String msg = "Counties loaded: "+count ;
updateMessage(msg);
}
return new LoadResults(data, counties) ;
}
}
}
public static void main(String[] args) {
launch();
}
}
module org.james_d.examples.covidcases {
requires javafx.controls;
requires javafx.fxml;
requires com.opencsv;
opens org.james_d.examples.covidcases to javafx.fxml;
exports org.james_d.examples.covidcases;
}
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>org.james_d.examples</groupId>
<artifactId>covidcases</artifactId>
<version>1.0-SNAPSHOT</version>
<name>covidcases</name>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<junit.version>5.8.1</junit.version>
</properties>
<dependencies>
<dependency>
<groupId>org.openjfx</groupId>
<artifactId>javafx-controls</artifactId>
<version>17.0.1</version>
</dependency>
<dependency>
<groupId>org.openjfx</groupId>
<artifactId>javafx-fxml</artifactId>
<version>17.0.1</version>
</dependency>
<dependency>
<groupId>com.opencsv</groupId>
<artifactId>opencsv</artifactId>
<version>5.5.2</version>
</dependency>
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter-api</artifactId>
<version>${junit.version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter-engine</artifactId>
<version>${junit.version}</version>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.8.1</version>
<configuration>
<source>17</source>
<target>17</target>
</configuration>
</plugin>
<plugin>
<groupId>org.openjfx</groupId>
<artifactId>javafx-maven-plugin</artifactId>
<version>0.0.8</version>
<executions>
<execution>
<!-- Default configuration for running with: mvn clean javafx:run -->
<id>default-cli</id>
<configuration>
<mainClass>
org.james_d.examples.covidcases/org.james_d.examples.covidcases.CovidCases
</mainClass>
<launcher>app</launcher>
<jlinkZipName>app</jlinkZipName>
<jlinkImageName>app</jlinkImageName>
<noManPages>true</noManPages>
<stripDebug>true</stripDebug>
<noHeaderFiles>true</noHeaderFiles>
</configuration>
</execution>
</executions>
</plugin>
</plugins>
</build>
</project>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment