diff --git a/webapp/src/Schema.tsx b/webapp/src/Schema.tsx
index d2f88d70e5ef607aab24df452c2a39d01cfa586c..380237d6ce31f4416eacc14800b58ed0daede75b 100644
--- a/webapp/src/Schema.tsx
+++ b/webapp/src/Schema.tsx
@@ -27,26 +27,22 @@ export interface Budget {
}
export interface FundingSource {
- CLIENT_INSTITUTIONS: string,
- COMMERCIAL: string,
- EUROPEAN_FUNDING: string,
- GOV_PUBLIC_BODIES: string,
+ CLIENT_INSTITUTIONS: number,
+ COMMERCIAL: number,
+ EUROPEAN_FUNDING: number,
+ GOV_PUBLIC_BODIES: number,
+ OTHER: number,
NREN: string,
- OTHER: string,
YEAR: number,
id: number
}
-export interface FS {
- data: [FundingSource]
-}
-
-export interface FundingGraphMatrix {
+export interface FundingSourceDataset {
labels: string[],
datasets: {
label: string,
- data: string[],
+ data: number[],
backgroundColor: string
borderRadius: number,
borderSkipped: boolean,
diff --git a/webapp/src/helpers/dataconversion.tsx b/webapp/src/helpers/dataconversion.tsx
new file mode 100644
index 0000000000000000000000000000000000000000..a3bb36acbf80f48c191167a108155336f6d4695f
--- /dev/null
+++ b/webapp/src/helpers/dataconversion.tsx
@@ -0,0 +1,102 @@
+import { cartesianProduct } from 'cartesian-product-multiple-arrays';
+
+import {
+ FundingSource,
+ FundingSourceDataset
+} from "../Schema";
+
+const DEFAULT_FUNDING_SOURCE_DATA = [
+ {
+ "CLIENT_INSTITUTIONS": "0.0",
+ "COMMERCIAL": "0.0",
+ "EUROPEAN_FUNDING": "0.0",
+ "GOV_PUBLIC_BODIES": "0.0",
+ "NREN": "",
+ "OTHER": "0.0",
+ "YEAR": 0,
+ "id": 0
+ }]
+
+function getColorMap() {
+ const rgbToHex = (r: number, g: number, b: number) => '#' + [r, g, b].map(x => {
+ const hex = x.toString(16)
+ return hex.length === 1 ? '0' + hex : hex
+ }).join('')
+
+ let colorMap = new Map<string, string>();
+ colorMap.set("CLIENT INSTITUTIONS", rgbToHex(157, 40, 114))
+ colorMap.set("COMMERCIAL", rgbToHex(241, 224, 79))
+ colorMap.set("EUROPEAN FUNDING", rgbToHex(219, 42, 76))
+ colorMap.set("GOV/PUBLIC_BODIES", rgbToHex(237, 141, 24))
+ colorMap.set("OTHER", rgbToHex(137, 166, 121))
+ return colorMap
+}
+
+function CreateDataLookup(data: FundingSource[]) {
+ let dataLookup = new Map<string, Map<string, number>>();
+
+ data.forEach((item: FundingSource) => {
+ const lookupKey = `${item.NREN}/${item.YEAR}`
+
+ let fundingSourceMap = dataLookup.get(lookupKey)
+ if (!fundingSourceMap) {
+ fundingSourceMap = new Map<string, number>();
+ }
+ fundingSourceMap.set("CLIENT INSTITUTIONS", item.CLIENT_INSTITUTIONS)
+ fundingSourceMap.set("COMMERCIAL", item.COMMERCIAL)
+ fundingSourceMap.set("EUROPEAN FUNDING", item.EUROPEAN_FUNDING)
+ fundingSourceMap.set("GOV/PUBLIC_BODIES", item.GOV_PUBLIC_BODIES)
+ fundingSourceMap.set("OTHER", item.OTHER)
+ dataLookup.set(lookupKey, fundingSourceMap)
+ })
+ return dataLookup
+}
+
+export const createFundingSourceDataset = (fundingSourcesData: FundingSource[]) => {
+ const data = fundingSourcesData ?? DEFAULT_FUNDING_SOURCE_DATA;
+ const dataLookup = CreateDataLookup(data)
+
+ const labelsYear = [...new Set(data.map((item: FundingSource) => item.YEAR))];
+ const labelsNREN = [...new Set(data.map((item: FundingSource) => item.NREN))];
+ const fundingSources = [
+ "CLIENT INSTITUTIONS",
+ "COMMERCIAL",
+ "EUROPEAN FUNDING",
+ "GOV/PUBLIC_BODIES",
+ "OTHER"
+ ]
+ const fundingSourcesPerYear = cartesianProduct(fundingSources, labelsYear)
+
+ const colorMap = getColorMap();
+ const fundingSourceDataset = fundingSourcesPerYear.map(function ([fundingSource, year]) {
+
+ const color = colorMap.get(fundingSource)!;
+ return {
+ backgroundColor: color,
+ label: fundingSource + "(" + year + ")",
+ data: labelsNREN.map(nren => {
+ // ensure that the data is in the same order as the labels
+ const lookupKey = `${nren}/${year}`
+ const dataForYear = dataLookup.get(lookupKey)
+ if (!dataForYear) {
+ return 0
+ }
+ return dataForYear.get(fundingSource) ?? 0
+ }),
+ stack: year,
+ borderRadius: 10,
+ borderSkipped: false,
+ barPercentage: 0.5,
+ borderWidth: 0.5,
+ categoryPercentage: 0.8
+
+ }
+ })
+
+ const dataResponse: FundingSourceDataset = {
+ // datasets: datasetFunding,
+ datasets: fundingSourceDataset,
+ labels: labelsNREN.map(l => l.toString())
+ }
+ return dataResponse;
+}
\ No newline at end of file
diff --git a/webapp/src/pages/FundingSource.tsx b/webapp/src/pages/FundingSource.tsx
index 1514cf6d46beb2f79dca9824fb66b0d5a40b9cab..cdc3db3282d835864b1cbab54c10b2c70efa7932 100644
--- a/webapp/src/pages/FundingSource.tsx
+++ b/webapp/src/pages/FundingSource.tsx
@@ -1,7 +1,6 @@
-import React, { ReactElement, useEffect, useState } from 'react';
-import { ChartOptions, scales, Tick } from 'chart.js';
+import React, { useEffect, useState } from 'react';
import { Bar } from 'react-chartjs-2';
-import { cartesianProduct } from 'cartesian-product-multiple-arrays';
+import { createFundingSourceDataset } from "../helpers/dataconversion";
import {
Chart as ChartJS,
@@ -13,14 +12,9 @@ import {
Legend,
} from 'chart.js';
import {
- Budget,
- BudgetMatrix,
- DataEntrySection,
FundingSource,
- FS, FundingGraphMatrix
+ FundingSourceDataset
} from "../Schema";
-// import _default from "chart.js/dist/plugins/plugin.tooltip";
-// import numbers = _default.defaults.animations.numbers;
ChartJS.register(
@@ -32,15 +26,34 @@ ChartJS.register(
Legend
);
-export const option = {
+const EMPTY_DATASET = {
+ datasets: [
+ {
+ backgroundColor: '',
+ data: [],
+ label: '',
+ borderRadius: 0,
+ borderSkipped: false,
+ barPercentage: 0,
+ borderWidth: 0,
+ stack: '0',
+ categoryPercentage: 0.5
+ }],
+ labels: []
+}
+
+export const chartOptions = {
+ maintainAspectRatio: false,
plugins: {
legend: {
+ display: false,
labels: {
boxWidth: 20,
boxHeight: 30,
pointStyle: "rectRounded",
borderRadius: 6,
useBorderRadius: true,
+
},
},
},
@@ -60,209 +73,48 @@ export const option = {
stacked: true,
},
},
- indexAxis: 'y',
+ indexAxis: "y" as const
};
-
-
-
-function FundingSourcePage(): ReactElement {
- function api<T>(url: string, options: RequestInit | undefined = undefined): Promise<T> {
- return fetch(url, options)
- .then((response) => {
- if (!response.ok) {
- return response.text().then((message) => {
- console.error(`Failed to load datax: ${message}`, response.status);
- throw new Error("The data could not be loaded, check the logs for details.");
- });
- }
-
- return response.json() as Promise<T>;
- })
+async function getData(): Promise<FundingSource[]> {
+ try {
+ const response = await fetch('/api/funding/');
+ return response.json();
+ } catch (error) {
+ console.error(`Failed to load data: ${error}`);
+ throw error;
}
+}
- const [fundingMatrixResponse, setFundingMatrixResponse] = useState<FundingGraphMatrix>();
- const [fundingSourceResponse, setFundingSource] = useState<FundingSource[]>();
- const [dataEntrySection, setDataEntrySection] = useState<DataEntrySection>();
- const [selectedDataEntry, setSelectedDataEntry] = useState<number>(0);
+function FundingSourcePage() {
+ const [fundingSourceDataset, setDataset] = useState<FundingSourceDataset>();
+ const [fundingSourceData, setFundingSourceData] = useState<FundingSource[]>();
useEffect(() => {
const loadData = async () => {
- if (fundingSourceResponse == undefined) {
- api<FS>('/api/funding/', {})
- .then((fundingSources: FS) => {
- console.log('fundingSource:', fundingSources)
- const entry = dataEntrySection?.items.find(i => i.id == selectedDataEntry)
- console.log(selectedDataEntry, dataEntrySection, entry)
- if (entry)
- console.log("hello")
- // options.plugins.title.text = entry.title;
- setFundingSource(fundingSources.data)
- convertToFundingSourcePerYearDataResponse(fundingSources.data)
- })
- .catch(error => {
- console.log(`Error fetching from API: ${error}`);
- })
- } else {
- convertToFundingSourcePerYearDataResponse(fundingSourceResponse)
- }
-
+ const _fundingData = await getData()
+ setFundingSourceData(_fundingData)
}
loadData()
}, [])
- const empty_funding_source_response = [
- {
- "CLIENT_INSTITUTIONS": "0.0",
- "COMMERCIAL": "0.0",
- "EUROPEAN_FUNDING": "0.0",
- "GOV_PUBLIC_BODIES": "0.0",
- "NREN": "",
- "OTHER": "0.0",
- "YEAR": 0,
- "id": 0
- }]
-
- const convertToFundingSourcePerYearDataResponse = (fundingSourcesResponse: FundingSource[]) => {
- const fsResponse = fundingSourcesResponse != undefined ? fundingSourcesResponse : empty_funding_source_response;
- const labelsYear = [...new Set(fsResponse.map((item: FundingSource) => item.YEAR))];
- const labelsNREN = [...new Set(fsResponse.map((item: FundingSource) => item.NREN))];
- const fundingComposition = [
- "CLIENT INSTITUTIONS",
- "COMMERCIAL",
- "EUROPEAN FUNDING",
- "GOV/PUBLIC_BODIES",
- "OTHER"
- ]
- const dataSetKey = cartesianProduct(fundingComposition, labelsYear)
- console.log("Nrens : ", labelsNREN)
- console.log("Years : ", labelsYear)
- console.log(dataSetKey);
-
- function getRandomColor() {
- const red = Math.floor(Math.random() * 256).toString(16).padStart(2, '0'); // generates a value between 00 and ff
- const green = Math.floor(Math.random() * 256).toString(16).padStart(2, '0');
- const blue = Math.floor(Math.random() * 256).toString(16).padStart(2, '0');
- return `#${red}${green}${blue}`;
- }
-
- const rgbToHex = (r: number, g: number, b: number) => '#' + [r, g, b].map(x => {
- const hex = x.toString(16)
- return hex.length === 1 ? '0' + hex : hex
- }).join('')
-
- let colorMap = new Map<string, string>();
- colorMap.set("CLIENT INSTITUTIONS", rgbToHex(157, 40, 114))
- colorMap.set("COMMERCIAL", rgbToHex(241, 224, 79))
- colorMap.set("EUROPEAN FUNDING", rgbToHex(219, 42, 76))
- colorMap.set("GOV/PUBLIC_BODIES", rgbToHex(237, 141, 24))
- colorMap.set("OTHER", rgbToHex(137, 166, 121))
-
-
- const datasetFunding = dataSetKey.map(function (entry) {
-
- // const randomColor = getRandomColor();
- const color: string = colorMap.get(entry[0])!;
- console.log(color)
- return {
- backgroundColor: color,
- label: entry[0] + "(" + entry[1] + ")",//composition+year
- data: labelsNREN.map(nren => dataPerCompositionPerYear(entry[1], nren, entry[0])),
- stack: entry[1],
- borderRadius: 10,
- borderSkipped: false,
- barPercentage: 0.5,
- borderWidth: 0.5,
- categoryPercentage: 0.8
-
- }
- })
-
- function dataPerCompositionPerYear(year: number, nren: string, composition: string) {
- let compValue = ""
- fsResponse.find(function (entry, index) {
- if (entry.YEAR == year && entry.NREN == nren) {
- if (composition === "CLIENT INSTITUTIONS")
- compValue = String(entry.CLIENT_INSTITUTIONS);
- if (composition === "COMMERCIAL")
- compValue = entry.COMMERCIAL;
- if (composition === "EUROPEAN FUNDING")
- compValue = entry.EUROPEAN_FUNDING;
- if (composition === "GOV/PUBLIC_BODIES")
- compValue = entry.GOV_PUBLIC_BODIES;
- if (composition === "OTHER")
- compValue = entry.OTHER;
- }
- })
- console.log(compValue)
- return compValue;
+ useEffect(() => {
+ if (fundingSourceData != undefined) {
+ const dataset = createFundingSourceDataset(fundingSourceData);
+ setDataset(dataset);
}
- console.log(datasetFunding)
+ }, [fundingSourceData])
- const dataResponse: FundingGraphMatrix = {
- // datasets: datasetFunding,
- datasets: datasetFunding,
- labels: labelsNREN.map(l => l.toString())
- }
- setFundingMatrixResponse(dataResponse);
- }
- const empty_bar_response = {
- datasets: [
- {
- backgroundColor: '',
- data: [],
- label: '',
- borderRadius: 0,
- borderSkipped: false,
- barPercentage: 0,
- borderWidth: 0,
- stack: '0',
- categoryPercentage: 0.5
- }],
- labels: []
- }
- const fundingAPIResponse: FundingGraphMatrix = fundingMatrixResponse !== undefined
- ? fundingMatrixResponse : empty_bar_response;
+ const dataset: FundingSourceDataset = fundingSourceDataset ?? EMPTY_DATASET;
return (
<div className='center' >
- <div className="chart-container" style={{ position: 'relative', height: '300vh', 'width': '80vw' }}>
- <h1>Income Source</h1>
- <Bar data={fundingAPIResponse}
- //height={200}
- options={{
- maintainAspectRatio: false,
- plugins: {
- legend: {
- display: false,
- labels: {
- boxWidth: 20,
- boxHeight: 30,
- pointStyle: "rectRounded",
- borderRadius: 6,
- useBorderRadius: true,
+ <h1 >Funding Source</h1>
- },
- },
- },
- scales: {
- x: {
- stacked: true,
- ticks: {
- callback: (value: string | number) => {
- if (typeof value === 'number') {
- return value.toFixed(2);
- }
- return value;
- },
- },
- },
- y: {
- stacked: true,
- },
- },
- indexAxis: "y",
- }}
- ></Bar>
+ <div className="chart-container" style={{ 'minHeight': '100vh', 'width': '60vw', }}>
+ <Bar
+ data={dataset}
+ options={chartOptions}
+ />
</div>
</div>