Contents

EECS3311: Country Data Visualization with Charts

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.java

Data 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 PatternChartFactory decouples 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