Skip to content
Snippets Groups Projects
Commit 76eef86e authored by Bjarke Madsen's avatar Bjarke Madsen
Browse files

Refactor FundingSource page

parent 46192842
No related branches found
No related tags found
No related merge requests found
...@@ -27,26 +27,22 @@ export interface Budget { ...@@ -27,26 +27,22 @@ export interface Budget {
} }
export interface FundingSource { export interface FundingSource {
CLIENT_INSTITUTIONS: string, CLIENT_INSTITUTIONS: number,
COMMERCIAL: string, COMMERCIAL: number,
EUROPEAN_FUNDING: string, EUROPEAN_FUNDING: number,
GOV_PUBLIC_BODIES: string, GOV_PUBLIC_BODIES: number,
OTHER: number,
NREN: string, NREN: string,
OTHER: string,
YEAR: number, YEAR: number,
id: number id: number
} }
export interface FS { export interface FundingSourceDataset {
data: [FundingSource]
}
export interface FundingGraphMatrix {
labels: string[], labels: string[],
datasets: { datasets: {
label: string, label: string,
data: string[], data: number[],
backgroundColor: string backgroundColor: string
borderRadius: number, borderRadius: number,
borderSkipped: boolean, borderSkipped: boolean,
......
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
import React, { ReactElement, useEffect, useState } from 'react'; import React, { useEffect, useState } from 'react';
import { ChartOptions, scales, Tick } from 'chart.js';
import { Bar } from 'react-chartjs-2'; import { Bar } from 'react-chartjs-2';
import { cartesianProduct } from 'cartesian-product-multiple-arrays'; import { createFundingSourceDataset } from "../helpers/dataconversion";
import { import {
Chart as ChartJS, Chart as ChartJS,
...@@ -13,14 +12,9 @@ import { ...@@ -13,14 +12,9 @@ import {
Legend, Legend,
} from 'chart.js'; } from 'chart.js';
import { import {
Budget,
BudgetMatrix,
DataEntrySection,
FundingSource, FundingSource,
FS, FundingGraphMatrix FundingSourceDataset
} from "../Schema"; } from "../Schema";
// import _default from "chart.js/dist/plugins/plugin.tooltip";
// import numbers = _default.defaults.animations.numbers;
ChartJS.register( ChartJS.register(
...@@ -32,15 +26,34 @@ ChartJS.register( ...@@ -32,15 +26,34 @@ ChartJS.register(
Legend 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: { plugins: {
legend: { legend: {
display: false,
labels: { labels: {
boxWidth: 20, boxWidth: 20,
boxHeight: 30, boxHeight: 30,
pointStyle: "rectRounded", pointStyle: "rectRounded",
borderRadius: 6, borderRadius: 6,
useBorderRadius: true, useBorderRadius: true,
}, },
}, },
}, },
...@@ -60,209 +73,48 @@ export const option = { ...@@ -60,209 +73,48 @@ export const option = {
stacked: true, stacked: true,
}, },
}, },
indexAxis: 'y', indexAxis: "y" as const
}; };
async function getData(): Promise<FundingSource[]> {
try {
const response = await fetch('/api/funding/');
function FundingSourcePage(): ReactElement { return response.json();
function api<T>(url: string, options: RequestInit | undefined = undefined): Promise<T> { } catch (error) {
return fetch(url, options) console.error(`Failed to load data: ${error}`);
.then((response) => { throw error;
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>;
})
} }
}
const [fundingMatrixResponse, setFundingMatrixResponse] = useState<FundingGraphMatrix>(); function FundingSourcePage() {
const [fundingSourceResponse, setFundingSource] = useState<FundingSource[]>(); const [fundingSourceDataset, setDataset] = useState<FundingSourceDataset>();
const [dataEntrySection, setDataEntrySection] = useState<DataEntrySection>(); const [fundingSourceData, setFundingSourceData] = useState<FundingSource[]>();
const [selectedDataEntry, setSelectedDataEntry] = useState<number>(0);
useEffect(() => { useEffect(() => {
const loadData = async () => { const loadData = async () => {
if (fundingSourceResponse == undefined) { const _fundingData = await getData()
api<FS>('/api/funding/', {}) setFundingSourceData(_fundingData)
.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)
}
} }
loadData() loadData()
}, []) }, [])
const empty_funding_source_response = [ useEffect(() => {
{ if (fundingSourceData != undefined) {
"CLIENT_INSTITUTIONS": "0.0", const dataset = createFundingSourceDataset(fundingSourceData);
"COMMERCIAL": "0.0", setDataset(dataset);
"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;
} }
console.log(datasetFunding) }, [fundingSourceData])
const dataResponse: FundingGraphMatrix = { const dataset: FundingSourceDataset = fundingSourceDataset ?? EMPTY_DATASET;
// 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;
return ( return (
<div className='center' > <div className='center' >
<div className="chart-container" style={{ position: 'relative', height: '300vh', 'width': '80vw' }}> <h1 >Funding Source</h1>
<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,
}, <div className="chart-container" style={{ 'minHeight': '100vh', 'width': '60vw', }}>
}, <Bar
}, data={dataset}
scales: { options={chartOptions}
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> </div>
</div> </div>
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment