Last active
February 11, 2022 04:33
-
-
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.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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(); | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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; | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<?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