EECS3311: Country Data Visualization with Charts
Contents
EECS3311: Country Data Visualization with Charts
3311project is a Java application developed for the EECS 3311 Software Engineering course. It fetches and visualizes country-level data (population, GDP, health indicators, etc.) across multiple years using various chart types.
Project Goals
- Apply software engineering principles (design patterns, separation of concerns) in a real Java project
- Build a multi-chart data visualization application
- Work with external data APIs and handle data transformation
Architecture
The project follows an MVC-like structure:
src/
├── model/
│ ├── Country.java
│ ├── DataFetcher.java
│ └── Indicator.java
├── view/
│ ├── ChartFactory.java
│ ├── BarChartView.java
│ ├── LineChartView.java
│ ├── PieChartView.java
│ └── ScatterPlotView.java
├── controller/
│ └── AnalysisController.java
└── Main.javaData Model
public class Country {
private final String code;
private final String name;
private final Map<Integer, Map<Indicator, Double>> data;
public Country(String code, String name) {
this.code = code;
this.name = name;
this.data = new HashMap<>();
}
public void addDataPoint(int year, Indicator indicator, double value) {
data.computeIfAbsent(year, k -> new HashMap<>())
.put(indicator, value);
}
public Optional<Double> getValue(int year, Indicator indicator) {
return Optional.ofNullable(data.get(year))
.map(yearData -> yearData.get(indicator));
}
}
public enum Indicator {
POPULATION,
GDP_PER_CAPITA,
LIFE_EXPECTANCY,
CO2_EMISSIONS,
FOREST_AREA,
INTERNET_USERS
}Chart Factory (Strategy Pattern)
public interface ChartView {
JPanel render(List<Country> countries, Indicator indicator,
int startYear, int endYear);
}
public class ChartFactory {
public static ChartView create(ChartType type) {
return switch (type) {
case LINE -> new LineChartView();
case BAR -> new BarChartView();
case PIE -> new PieChartView();
case SCATTER -> new ScatterPlotView();
};
}
}Line Chart View
public class LineChartView implements ChartView {
@Override
public JPanel render(List<Country> countries, Indicator indicator,
int startYear, int endYear) {
XYSeriesCollection dataset = new XYSeriesCollection();
for (Country country : countries) {
XYSeries series = new XYSeries(country.getName());
for (int year = startYear; year <= endYear; year++) {
country.getValue(year, indicator)
.ifPresent(val -> series.add(year, val));
}
dataset.addSeries(series);
}
JFreeChart chart = ChartFactory.createXYLineChart(
indicator.getDisplayName() + " Over Time",
"Year", indicator.getUnit(),
dataset,
PlotOrientation.VERTICAL,
true, true, false
);
return new ChartPanel(chart);
}
}World Bank API Integration
public class DataFetcher {
private static final String BASE_URL =
"https://api.worldbank.org/v2/country/%s/indicator/%s?format=json&per_page=100";
public Map<Integer, Double> fetchIndicator(String countryCode, String indicatorCode)
throws IOException {
String url = String.format(BASE_URL, countryCode, indicatorCode);
HttpURLConnection conn = (HttpURLConnection) new URL(url).openConnection();
conn.setRequestMethod("GET");
try (BufferedReader reader = new BufferedReader(
new InputStreamReader(conn.getInputStream()))) {
StringBuilder sb = new StringBuilder();
String line;
while ((line = reader.readLine()) != null) sb.append(line);
return parseWorldBankResponse(sb.toString());
}
}
private Map<Integer, Double> parseWorldBankResponse(String json) {
JSONArray records = new JSONArray(json).getJSONArray(1);
Map<Integer, Double> result = new TreeMap<>();
for (int i = 0; i < records.length(); i++) {
JSONObject record = records.getJSONObject(i);
if (!record.isNull("value")) {
int year = Integer.parseInt(record.getString("date"));
double val = record.getDouble("value");
result.put(year, val);
}
}
return result;
}
}Key Software Engineering Concepts Applied
- Strategy Pattern — interchangeable chart renderers behind a common interface
- Factory Pattern —
ChartFactorydecouples chart type selection from creation - MVC Separation — data fetching, rendering, and user interaction are cleanly separated
- Optional — safe handling of missing data points without null checks scattered through rendering logic
Lessons Learned
Building a charting application taught me the value of designing for extensibility early — adding a new chart type only requires implementing ChartView, not touching the controller or model. The World Bank API also introduced practical lessons in handling missing and inconsistent real-world data.
Source: GitHub