diff --git a/compendium_v2/routes/budget.py b/compendium_v2/routes/budget.py index 7c5090ade591a0eb77736d5226bac9b8208d94bb..005c48eabb3d5bf7948837a5fbcc5a704ad1d8e1 100644 --- a/compendium_v2/routes/budget.py +++ b/compendium_v2/routes/budget.py @@ -101,5 +101,5 @@ def budget_view() -> Any: with db.session_scope() as session: entries = sorted([_extract_data(entry) for entry in session.query(model.BudgetEntry)], key=lambda d: (d['BUDGET_YEAR'], d['NREN'])) - - return jsonify(entries) + dict_obj = {"data": entries} + return jsonify(dict_obj) diff --git a/webapp/src/Schema.tsx b/webapp/src/Schema.tsx index 905dd1d3487017a6ca18571013a1993976a15895..9105f43db1625ab0b16f50f178b5804e35d0f866 100644 --- a/webapp/src/Schema.tsx +++ b/webapp/src/Schema.tsx @@ -26,6 +26,16 @@ export interface BudgetMatrix{ title:string } +export interface Budget{ + data: { + BUDGET: string, + BUDGET_YEAR: number, + NREN: string, + id: number + }[] +} + + export interface DataEntrySection { name: string, description: string, diff --git a/webapp/src/pages/DataAnalysis.tsx b/webapp/src/pages/DataAnalysis.tsx index ce181ba74e3ccc1b8f4b182ff83c016d29a5f4dc..44779b2b96821b566d85ae1a2e9bcf9ef9ede1e7 100644 --- a/webapp/src/pages/DataAnalysis.tsx +++ b/webapp/src/pages/DataAnalysis.tsx @@ -3,8 +3,9 @@ import {Accordion, Col, Container, ListGroup, Row} from "react-bootstrap"; import BarGraph from "../components/graphing/BarGraph"; import LineGraph from "../components/graphing/LineGraph"; - -import {BudgetMatrix, DataEntrySection} from "../Schema"; +import {BudgetMatrix, DataEntrySection,Budget} from "../Schema"; +// import {evaluateInteractionItems} from "chart.js/dist/core/core.interaction"; +import barGraph from "../components/graphing/BarGraph"; export const options = { @@ -35,84 +36,201 @@ function DataAnalysis(): ReactElement { return response.json() as Promise<T>; }) } - - const [budgetResponse, setBudgetMatrix] = useState<BudgetMatrix>(); + const [budgetMatrixResponse, setBudgetMatrixResponse] = useState<BudgetMatrix>(); + const [budgetResponse, setBudget] = useState<Budget>(); const [dataEntrySection, setDataEntrySection] = useState<DataEntrySection>(); const [selectedDataEntry, setSelectedDataEntry] = useState<number>(0); - // const [services, setServices] = useState<Service[][]>(); - useEffect(() => { // let timeoutRef = 0; const getDataEntries = () => { - api<DataEntrySection>('/api/data-entries/sections/1', { - referrerPolicy: "unsafe-url", - headers: { - "Access-Control-Allow-Origin": "*", - "Content-Type": "text/plain" - } - }).then( (dataEntrySectionResponse: DataEntrySection) => { - setDataEntrySection(dataEntrySectionResponse); - // Autoload the first graph - if (dataEntrySectionResponse.items.length > 0) { - setSelectedDataEntry(dataEntrySectionResponse.items[0].id); - } - }) + const dataEntrySectionResponse: DataEntrySection={ + description:"Org", + items:[ + { + id:2, + title:"NREN Budgets per year, in Millions EUR", + url:"/api/data-entries/item/2" + } + // { + // id:3, + // title:"NREN Budgets per NREN, in Millions EUR", + // url:"/api/data-entries/item/3" + // } + ], + name:"Organisation" + } + setDataEntrySection(dataEntrySectionResponse); + console.log("getDataEntries "+dataEntrySection); + // Autoload the first graph + if (dataEntrySectionResponse.items.length > 0) { + setSelectedDataEntry(dataEntrySectionResponse.items[0].id); + } } const loadData = () => { + console.log("selectedDataEntry "+selectedDataEntry) if (selectedDataEntry == 0) { getDataEntries(); return; } + console.log("budgetResponse "+budgetResponse) + if(budgetResponse==undefined) { + api<Budget>('/api/budget', { + referrerPolicy: "unsafe-url", + headers: { + "Access-Control-Allow-Origin": "*", + "Content-Type": "text/plain", + "Access-Control-Allow-Methods": "GET, POST, PATCH, PUT, DELETE, OPTIONS", + "Access-Control-Allow-Headers": "Origin, Content-Type, X-Auth-Token" - api<BudgetMatrix>('/api/budget' + selectedDataEntry,{ - referrerPolicy: "unsafe-url", - headers: { - "Access-Control-Allow-Origin": "*", - "Content-Type": "text/plain" - } - }) - .then((budgetMatrix :BudgetMatrix)=>{ - options.plugins.title.text = budgetMatrix.title; - setBudgetMatrix(budgetMatrix) + } }) + .then((budget: Budget) => { + if (selectedDataEntry == 2) + options.plugins.title.text = 'NREN Budgets per year, in Millions EUR'; + setBudget(budget) + console.log("API positive response Budget : "+ budget) + convertToBudgetPerYearDataResponse(budget) + }) + .catch(error => { + console.log(`Error fetching from API: ${error}`); + }) + }else { + convertToBudgetPerYearDataResponse(budgetResponse) + } } loadData() }, [selectedDataEntry]); const empty_bar_response = { - 'data': { - 'datasets': [ - { - 'backgroundColor':'', - 'data':[], - 'label':'' - }], - 'labels':[] - }, - 'description': "", - 'id': "", - 'settings':{}, - 'title':'' -} + data: { + datasets: [ + { + backgroundColor:'', + data:[], + label:'' + }], + labels:[] + }, + description: "", + id: "", + settings:{}, + title:"" + } + + const empty_budget_response = { + data: [{ + BUDGET: "", + BUDGET_YEAR: 0, + NREN: "", + id: 0 + }] + } + + + const convertToBudgetPerYearDataResponse = (budgetResponse:Budget) => { + console.log("convertToBudgetPerYearDataResponse budgetResponse "+budgetResponse); + const barResponse=budgetResponse!=undefined?budgetResponse:empty_budget_response; + const labelsYear = [...new Set(barResponse.data.map((item ) => item.BUDGET_YEAR))]; + const labelsNREN = [...new Set(barResponse.data.map((item ) => item.NREN))]; -const barResponse: BudgetMatrix = budgetResponse !== undefined ? budgetResponse : empty_bar_response; + console.log("convertToBudgetPerYearDataResponse "+barResponse); + 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}`; + } + + + function dataForNRENForYear(year:number,nren :string){ + const budget = barResponse.data.find(function (entry,index){ + if(entry.BUDGET_YEAR== year && entry.NREN== nren) { + return Number(entry.BUDGET); + } + }) + return budget !== undefined?Number(budget.BUDGET):null; + } + + const datasetPerYear = labelsYear.map(function (year){ + const randomColor = getRandomColor(); + return { + backgroundColor: randomColor, + borderColor:randomColor, + data: labelsNREN.map(nren => dataForNRENForYear(year,nren)), + label:year.toString() + } + }) + + const datasetPerNREN = labelsNREN.map(function (nren){ + const randomColor = getRandomColor(); + return { + backgroundColor: randomColor, + borderColor:randomColor, + data: labelsYear.map(year => dataForNRENForYear(year,nren)), + label:nren + } + }) + + if(selectedDataEntry==2){ + const dataResponse : BudgetMatrix ={ + data: { + datasets: datasetPerNREN, + labels: labelsYear.map(l=>l.toString()) + }, + description: "The numbers are based on 30 NRENs that " + + "reported their budgets continuously throughout this" + + " period. This means that some larger NRENs are not" + + " included and therefore the actual total budgets will" + + " have been higher. (For comparison, the total budget" + + " according to the 2021 survey results based on the data" + + " for all responding NRENs that year is €555 M). The" + + " percentage change is based on the previous year’s" + + " budget.", + id: "3", + settings:{}, + title:'NREN Budgets per NREN, in Millions EUR' + } + setBudgetMatrixResponse(dataResponse); + } + else{ + const dataResponse : BudgetMatrix ={ + data: { + datasets: datasetPerYear, + labels: labelsNREN.map(l=>l.toString()) + }, + description: + "The numbers are based on 30 NRENs that reported their " + + "budgets continuously throughout this period. This " + + "means that some larger NRENs are not included and " + + "therefore the actual total budgets will have been " + + "higher. (For comparison, the total budget according to" + + " the 2021 survey results based on the data for all" + + " responding NRENs that year is €555 M). The percentage" + + " change is based on the previous year’s budget.", + id: "2", + settings:{}, + title:'NREN Budgets per year, in Millions EUR' + } + setBudgetMatrixResponse(dataResponse); + } + } + + const barResponse: BudgetMatrix = budgetMatrixResponse !== undefined + ? budgetMatrixResponse : empty_bar_response; return ( <div> <h1>Data Analysis</h1> <Container> <Row> <Col> - <Row> - <BarGraph data={barResponse.data} /> - </Row> <Row> <LineGraph data={barResponse.data} /> </Row> - <Row>{budgetResponse?.description}</Row> + <Row>{budgetMatrixResponse?.description}</Row> </Col> <Col xs={3}> diff --git a/webapp/tsconfig.json b/webapp/tsconfig.json index e64e176fe41e6e2748cc1044ca3415fa9bed453d..52b6b1dad80ca94c7033733afede251dd4cf67c4 100644 --- a/webapp/tsconfig.json +++ b/webapp/tsconfig.json @@ -1,5 +1,6 @@ { "compilerOptions": { + "target": "ES6", "lib": ["dom", "dom.iterable", "esnext"], "allowJs": true, "allowSyntheticDefaultImports": true, diff --git a/webapp/webpack.config.ts b/webapp/webpack.config.ts index 31f81894fdb7e6cf28c4c2197cfcd4c2b0adc16e..50d8c52bad1c114e4b9dec315e2d74c8617cd9a9 100644 --- a/webapp/webpack.config.ts +++ b/webapp/webpack.config.ts @@ -85,7 +85,7 @@ const config: Configuration = { // Allow SPA urls to work with dev-server historyApiFallback: true, proxy: { - "/api": "http://127.0.0.1:5000", + "/api": "http://127.0.0.1:33333", }, }, plugins: [