GovernmentBodyChartDataManagerImpl.java
- /*
- * Copyright 2010-2025 James Pether Sörling
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- *
- * $Id$
- * $HeadURL$
- */
- package com.hack23.cia.web.impl.ui.application.views.common.chartfactory.impl;
- import java.util.Collections;
- import java.util.Comparator;
- import java.util.List;
- import java.util.Locale;
- import java.util.Map;
- import java.util.Objects;
- import java.util.Optional;
- import java.util.stream.Collectors;
- import org.dussan.vaadin.dcharts.DCharts;
- import org.dussan.vaadin.dcharts.base.elements.XYseries;
- import org.dussan.vaadin.dcharts.data.DataSeries;
- import org.dussan.vaadin.dcharts.options.Series;
- import org.springframework.beans.factory.annotation.Autowired;
- import org.springframework.stereotype.Service;
- import com.hack23.cia.service.external.esv.api.EsvApi;
- import com.hack23.cia.service.external.esv.api.GovernmentBodyAnnualOutcomeSummary;
- import com.hack23.cia.service.external.esv.api.GovernmentBodyAnnualSummary;
- import com.hack23.cia.web.impl.ui.application.views.common.chartfactory.api.GovernmentBodyChartDataManager;
- import com.vaadin.ui.AbstractOrderedLayout;
- import com.vaadin.ui.VerticalLayout;
- /**
- * The Class GovernmentBodyChartDataManagerImpl.
- */
- @Service
- public final class GovernmentBodyChartDataManagerImpl extends AbstractChartDataManagerImpl
- implements GovernmentBodyChartDataManager {
- /** The Constant ALL_GOVERNMENT_BODIES. */
- private static final String ALL_GOVERNMENT_BODIES = "All government bodies";
- /** The Constant ANNUAL_EXPENDITURE. */
- private static final String ANNUAL_EXPENDITURE = "Annual Expenditure";
- /** The Constant ANNUAL_HEADCOUNT. */
- private static final String ANNUAL_HEADCOUNT = "Annual headcount";
- /** The Constant ANNUAL_HEADCOUNT_ALL_MINISTRIES. */
- private static final String ANNUAL_HEADCOUNT_ALL_MINISTRIES = "Annual headcount, all ministries";
- /** The Constant ANNUAL_HEADCOUNT_SUMMARY_ALL_GOVERNMENT_BODIES. */
- private static final String ANNUAL_HEADCOUNT_SUMMARY_ALL_GOVERNMENT_BODIES =
- "Annual headcount summary, all government bodies";
- /** The Constant ANNUAL_HEADCOUNT_TOTAL_ALL_GOVERNMENT_BODIES. */
- private static final String ANNUAL_HEADCOUNT_TOTAL_ALL_GOVERNMENT_BODIES =
- "Annual headcount total all government bodies";
- /** The Constant ANNUAL_INCOME. */
- private static final String ANNUAL_INCOME = "Annual Income";
- /** The Constant EXPENDITURE_GROUP_NAME. */
- private static final String EXPENDITURE_GROUP_NAME = "Utgiftsområdesnamn";
- /** The Constant INKOMSTTITELGRUPPSNAMN. */
- private static final String INKOMSTTITELGRUPPSNAMN = "Inkomsttitelgruppsnamn";
- /** The Constant INKOMSTTITELSNAMN. */
- private static final String INKOMSTTITELSNAMN = "Inkomsttitelsnamn";
- /** The Constant ANSLAGSPOSTSNAMN. */
- private static final String ANSLAGSPOSTSNAMN = "Anslagspostsnamn";
- /** The esv api. */
- @Autowired
- private EsvApi esvApi;
- /**
- * Adds a data point to the DataSeries if the year and value are valid and value > 0.
- *
- * @param dataSeries the data series
- * @param year the year
- * @param value the value
- */
- private static void addDataPoint(final DataSeries dataSeries, final Integer year, final Number value) {
- if (dataSeries == null || year == null || value == null) {
- return;
- }
- final double doubleValue = value.doubleValue();
- if (doubleValue > 0) {
- // Ensure consistent date format: " 01-JAN-YYYY"
- final String formattedDate = String.format(Locale.ENGLISH," 01-JAN-%d", year);
- dataSeries.add(formattedDate, doubleValue);
- }
- }
- /**
- * Creates a chart using the provided data/series objects, then appends it to the given layout.
- *
- * @param layout the layout
- * @param label the label
- * @param dataSeries the data series
- * @param series the series
- */
- private void addChartToLayout(final AbstractOrderedLayout layout, final String label,
- final DataSeries dataSeries, final Series series) {
- Objects.requireNonNull(layout, "Layout cannot be null");
- Objects.requireNonNull(label, "Label cannot be null");
- final DCharts chart = new DCharts()
- .setDataSeries(dataSeries)
- .setOptions(getChartOptions().createOptionsXYDateFloatLogYAxisLegendOutside(series))
- .show();
- ChartUtils.addChart(layout, label, chart, true);
- }
- /**
- * Consolidates logic to retrieve, group, and process data by a descriptive field
- * (e.g., 'Utgiftsområdesnamn' or 'Inkomsttitelgruppsnamn'), feeding the results into a DataSeries.
- *
- * @param dataSeries the data series
- * @param series the series
- * @param groupedData the grouped data
- */
- private void buildAnnualOutcomeDataSeriesByField(final DataSeries dataSeries, final Series series,
- final Map<String, List<GovernmentBodyAnnualOutcomeSummary>> groupedData) {
- Optional.ofNullable(groupedData)
- .ifPresent(data -> data.entrySet().stream()
- .filter(entry -> entry.getValue() != null && !entry.getValue().isEmpty())
- .forEach(entry -> {
- series.addSeries(new XYseries().setLabel(entry.getKey()));
- dataSeries.newSeries();
- entry.getValue().stream()
- .filter(Objects::nonNull)
- .forEach(summary -> {
- // Only process if we have a valid year and value map
- Optional.ofNullable(summary.getValueMap())
- .ifPresent(valueMap -> {
- // Get the year directly from the summary
- final Integer year = summary.getYear();
- // Get the total value for this year
- final Double totalValue = valueMap.values().stream()
- .filter(Objects::nonNull)
- .mapToDouble(Number::doubleValue)
- .sum();
- // Only add if we have valid year and value
- if (year != null && totalValue > 0) {
- addDataPoint(dataSeries, year, totalValue);
- }
- });
- });
- }));
- }
- /**
- * Helper method to generate a headcount data series given a map of year -> list of summaries.
- *
- * @param dataSeries the data series
- * @param series the series
- * @param yearlyData the yearly data
- * @param label the label
- */
- private void buildHeadcountDataSeries(final DataSeries dataSeries, final Series series,
- final Map<Integer, List<GovernmentBodyAnnualSummary>> yearlyData,
- final String label) {
- Optional.ofNullable(yearlyData)
- .ifPresent(data -> {
- series.addSeries(new XYseries().setLabel(label));
- dataSeries.newSeries();
- data.entrySet().stream()
- .filter(entry -> Objects.nonNull(entry.getValue()) && !entry.getValue().isEmpty())
- .forEach(entry -> {
- final int totalHeadcount = entry.getValue().stream()
- .filter(Objects::nonNull)
- .mapToInt(GovernmentBodyAnnualSummary::getHeadCount)
- .sum();
- addDataPoint(dataSeries, entry.getKey(), totalHeadcount);
- });
- });
- }
- /**
- * Consolidates logic for creating a chart that is grouped by some string field in descriptionFields.
- *
- * @param layout the layout
- * @param field the field
- * @param chartLabel the chart label
- */
- private void createMinistryFieldSummary(final AbstractOrderedLayout layout, final String field, final String chartLabel) {
- Objects.requireNonNull(layout, "Layout cannot be null");
- Objects.requireNonNull(field, "Field cannot be null");
- Objects.requireNonNull(chartLabel, "Chart label cannot be null");
- final DataSeries dataSeries = new DataSeries();
- final Series series = new Series();
- Optional.ofNullable(esvApi.getGovernmentBodyReportByMinistry())
- .ifPresent(reportByMinistry ->
- reportByMinistry.entrySet().stream()
- .filter(entry -> entry.getValue() != null && !entry.getValue().isEmpty())
- .forEach(entry -> {
- final Map<Integer, Double> annualTotals = entry.getValue().stream()
- .filter(Objects::nonNull)
- .filter(summary -> Optional.ofNullable(summary.getDescriptionFields())
- .map(fields -> fields.get(field))
- .isPresent())
- .collect(Collectors.groupingBy(
- GovernmentBodyAnnualOutcomeSummary::getYear,
- Collectors.summingDouble(GovernmentBodyAnnualOutcomeSummary::getYearTotal)
- ));
- if (!annualTotals.isEmpty()) {
- series.addSeries(new XYseries().setLabel(entry.getKey()));
- dataSeries.newSeries();
- annualTotals.forEach((year, total) ->
- addDataPoint(dataSeries, year + 1, total));
- }
- }));
- addChartToLayout(layout, chartLabel, dataSeries, series);
- }
- // ==================== Public Chart Methods ====================
- @Override
- public void createGovernmentBodyExpenditureSummaryChart(final VerticalLayout layout) {
- Objects.requireNonNull(layout, "Layout cannot be null");
- final Map<String, List<GovernmentBodyAnnualOutcomeSummary>> report =
- esvApi.getGovernmentBodyReportByField(EXPENDITURE_GROUP_NAME);
- final DataSeries dataSeries = new DataSeries();
- final Series series = new Series();
- buildAnnualOutcomeDataSeriesByField(dataSeries, series, report);
- addChartToLayout(layout, ANNUAL_EXPENDITURE, dataSeries, series);
- }
- @Override
- public void createGovernmentBodyExpenditureSummaryChart(final VerticalLayout layout,
- final String governmentBodyName) {
- Objects.requireNonNull(layout, "Layout cannot be null");
- Objects.requireNonNull(governmentBodyName, "Government body name cannot be null");
- final Map<String, List<GovernmentBodyAnnualOutcomeSummary>> allSummaries = esvApi.getGovernmentBodyReport();
- final List<GovernmentBodyAnnualOutcomeSummary> summariesForBody = Optional.ofNullable(allSummaries)
- .map(summaries -> summaries.get(governmentBodyName))
- .orElse(Collections.emptyList());
- final Map<String, List<GovernmentBodyAnnualOutcomeSummary>> grouped = summariesForBody.stream()
- .filter(Objects::nonNull)
- .filter(summary -> Optional.ofNullable(summary.getDescriptionFields())
- .map(fields -> fields.get(ANSLAGSPOSTSNAMN))
- .isPresent())
- .sorted(Comparator.comparing(GovernmentBodyAnnualOutcomeSummary::getYear))
- .collect(Collectors.groupingBy(
- summary -> summary.getDescriptionFields().get(ANSLAGSPOSTSNAMN)));
- final DataSeries dataSeries = new DataSeries();
- final Series series = new Series();
- buildAnnualOutcomeDataSeriesByField(dataSeries, series, grouped);
- addChartToLayout(layout, governmentBodyName + " " + ANNUAL_EXPENDITURE, dataSeries, series);
- }
- @Override
- public void createGovernmentBodyHeadcountSummaryChart(final VerticalLayout layout) {
- Objects.requireNonNull(layout, "Layout cannot be null");
- final Map<Integer, List<GovernmentBodyAnnualSummary>> map = esvApi.getData();
- final DataSeries dataSeries = new DataSeries();
- final Series series = new Series();
- buildHeadcountDataSeries(dataSeries, series, map, ALL_GOVERNMENT_BODIES);
- addChartToLayout(layout, ANNUAL_HEADCOUNT_TOTAL_ALL_GOVERNMENT_BODIES, dataSeries, series);
- }
- @Override
- public void createGovernmentBodyHeadcountSummaryChart(final VerticalLayout layout,
- final String governmentBodyName) {
- Objects.requireNonNull(layout, "Layout cannot be null");
- Objects.requireNonNull(governmentBodyName, "Government body name cannot be null");
- final Map<Integer, GovernmentBodyAnnualSummary> map = esvApi.getDataPerGovernmentBody(governmentBodyName);
- final DataSeries dataSeries = new DataSeries();
- final Series series = new Series();
- series.addSeries(new XYseries().setLabel(governmentBodyName));
- dataSeries.newSeries();
- Optional.ofNullable(map)
- .ifPresent(yearSummaryMap -> yearSummaryMap.entrySet().stream()
- .filter(entry -> Objects.nonNull(entry.getValue()))
- .forEach(entry -> addDataPoint(dataSeries, entry.getKey(), entry.getValue().getHeadCount())));
- addChartToLayout(layout, governmentBodyName + " " + ANNUAL_HEADCOUNT, dataSeries, series);
- }
- @Override
- public void createGovernmentBodyIncomeSummaryChart(final VerticalLayout layout) {
- Objects.requireNonNull(layout, "Layout cannot be null");
- final Map<String, List<GovernmentBodyAnnualOutcomeSummary>> report =
- esvApi.getGovernmentBodyReportByField(INKOMSTTITELGRUPPSNAMN);
- final DataSeries dataSeries = new DataSeries();
- final Series series = new Series();
- buildAnnualOutcomeDataSeriesByField(dataSeries, series, report);
- addChartToLayout(layout, ANNUAL_INCOME, dataSeries, series);
- }
- @Override
- public void createGovernmentBodyIncomeSummaryChart(final VerticalLayout layout,
- final String governmentBodyName) {
- Objects.requireNonNull(layout, "Layout cannot be null");
- Objects.requireNonNull(governmentBodyName, "Government body name cannot be null");
- final Map<String, List<GovernmentBodyAnnualOutcomeSummary>> allSummaries = esvApi.getGovernmentBodyReport();
- final List<GovernmentBodyAnnualOutcomeSummary> summariesForBody = Optional.ofNullable(allSummaries)
- .map(summaries -> summaries.get(governmentBodyName))
- .orElse(Collections.emptyList());
- final Map<String, List<GovernmentBodyAnnualOutcomeSummary>> grouped = summariesForBody.stream()
- .filter(Objects::nonNull)
- .filter(summary -> Optional.ofNullable(summary.getDescriptionFields())
- .map(fields -> fields.get(INKOMSTTITELSNAMN))
- .isPresent())
- .sorted(Comparator.comparing(GovernmentBodyAnnualOutcomeSummary::getYear))
- .collect(Collectors.groupingBy(
- summary -> summary.getDescriptionFields().get(INKOMSTTITELSNAMN)));
- final DataSeries dataSeries = new DataSeries();
- final Series series = new Series();
- buildAnnualOutcomeDataSeriesByField(dataSeries, series, grouped);
- addChartToLayout(layout, governmentBodyName + " " + ANNUAL_INCOME, dataSeries, series);
- }
- @Override
- public void createMinistryGovernmentBodyExpenditureSummaryChart(final AbstractOrderedLayout layout) {
- Objects.requireNonNull(layout, "Layout cannot be null");
- createMinistryFieldSummary(layout, EXPENDITURE_GROUP_NAME, "MinistryGovernmentBodySpendingSummaryChart");
- }
- @Override
- public void createMinistryGovernmentBodyExpenditureSummaryChart(final VerticalLayout layout,
- final String governmentBodyName) {
- Objects.requireNonNull(layout, "Layout cannot be null");
- Objects.requireNonNull(governmentBodyName, "Government body name cannot be null");
- final Map<String, List<GovernmentBodyAnnualOutcomeSummary>> report =
- esvApi.getGovernmentBodyReportByFieldAndMinistry(EXPENDITURE_GROUP_NAME, governmentBodyName);
- final DataSeries dataSeries = new DataSeries();
- final Series series = new Series();
- buildAnnualOutcomeDataSeriesByField(dataSeries, series, report);
- addChartToLayout(layout, governmentBodyName + " " + ANNUAL_EXPENDITURE, dataSeries, series);
- }
- @Override
- public void createMinistryGovernmentBodyHeadcountSummaryChart(final AbstractOrderedLayout layout) {
- Objects.requireNonNull(layout, "Layout cannot be null");
- final Map<Integer, List<GovernmentBodyAnnualSummary>> map = esvApi.getData();
- final List<String> ministryNames = esvApi.getMinistryNames();
- final DataSeries dataSeries = new DataSeries();
- final Series series = new Series();
- Optional.ofNullable(ministryNames)
- .filter(names -> map != null)
- .ifPresent(names -> names.stream()
- .forEach(ministryName -> {
- series.addSeries(new XYseries().setLabel(ministryName));
- dataSeries.newSeries();
- map.entrySet().stream()
- .filter(entry -> entry.getValue() != null)
- .forEach(entry -> {
- final int headcount = entry.getValue().stream()
- .filter(Objects::nonNull)
- .filter(summary -> summary.getMinistry() != null)
- .filter(summary -> summary.getMinistry().equalsIgnoreCase(ministryName))
- .mapToInt(GovernmentBodyAnnualSummary::getHeadCount)
- .sum();
- addDataPoint(dataSeries, entry.getKey(), headcount);
- });
- }));
- addChartToLayout(layout, ANNUAL_HEADCOUNT_ALL_MINISTRIES, dataSeries, series);
- }
- @Override
- public void createMinistryGovernmentBodyHeadcountSummaryChart(final AbstractOrderedLayout layout,
- final String governmentBodyName) {
- Objects.requireNonNull(layout, "Layout cannot be null");
- Objects.requireNonNull(governmentBodyName, "Government body name cannot be null");
- final Map<Integer, List<GovernmentBodyAnnualSummary>> map = esvApi.getDataPerMinistry(governmentBodyName);
- final List<String> governmentBodyNames = esvApi.getGovernmentBodyNames(governmentBodyName);
- final DataSeries dataSeries = new DataSeries();
- final Series series = new Series();
- Optional.ofNullable(governmentBodyNames)
- .filter(names -> map != null)
- .ifPresent(names -> names.stream()
- .forEach(govBodyName -> {
- series.addSeries(new XYseries().setLabel(govBodyName));
- dataSeries.newSeries();
- map.entrySet().stream()
- .filter(entry -> entry.getValue() != null)
- .forEach(entry -> {
- final int headcount = entry.getValue().stream()
- .filter(Objects::nonNull)
- .filter(summary -> govBodyName.equalsIgnoreCase(summary.getName()))
- .mapToInt(GovernmentBodyAnnualSummary::getHeadCount)
- .sum();
- addDataPoint(dataSeries, entry.getKey(), headcount);
- });
- }));
- if (map != null && governmentBodyNames != null) {
- addChartToLayout(layout,
- governmentBodyName + " " + ANNUAL_HEADCOUNT_SUMMARY_ALL_GOVERNMENT_BODIES,
- dataSeries,
- series);
- }
- }
- @Override
- public void createMinistryGovernmentBodyIncomeSummaryChart(final AbstractOrderedLayout layout) {
- Objects.requireNonNull(layout, "Layout cannot be null");
- createMinistryFieldSummary(layout, INKOMSTTITELGRUPPSNAMN, "MinistryGovernmentBodyIncomeSummaryChart");
- }
- @Override
- public void createMinistryGovernmentBodyIncomeSummaryChart(final VerticalLayout layout,
- final String governmentBodyName) {
- Objects.requireNonNull(layout, "Layout cannot be null");
- Objects.requireNonNull(governmentBodyName, "Government body name cannot be null");
- final Map<String, List<GovernmentBodyAnnualOutcomeSummary>> report =
- esvApi.getGovernmentBodyReportByFieldAndMinistry(INKOMSTTITELGRUPPSNAMN, governmentBodyName);
- final DataSeries dataSeries = new DataSeries();
- final Series series = new Series();
- buildAnnualOutcomeDataSeriesByField(dataSeries, series, report);
- addChartToLayout(layout, governmentBodyName + " " + ANNUAL_INCOME, dataSeries, series);
- }
- }