Skip to content
Snippets Groups Projects
Commit 76ef0781 authored by Ian Galpin's avatar Ian Galpin
Browse files

Merge branch 'feature/COMP-83_line_graph_of_budgets' into 'develop'

Added line graphs and re-organised the webapp layout

See merge request live-projects/compendium-v2!11
parents 55057be4 251c2751
No related branches found
No related tags found
No related merge requests found
......@@ -54,3 +54,43 @@ def get_budget_by_year():
response_data['datasets'].append(dataset)
return response_data
def get_budget_by_nren():
budget_data = db_survey.session.execute(
db_survey.select(AnnualBudgetEntry)
.filter(AnnualBudgetEntry.region_name == 'Western Europe')
).scalars()
annual_data = {
}
seen_years = set()
for line_item in budget_data:
li_year = line_item.year
li_country_code = line_item.country_code
if li_country_code not in annual_data:
annual_data[li_country_code] = {}
seen_years.add(li_year)
annual_data[li_country_code][li_year] = line_item.budget
sorted_years = sorted(seen_years)
response_data = {
'labels': sorted_years,
'datasets': []
}
for country in sorted(annual_data.keys()):
dataset = {
'label': country,
'data': []
}
for year in sorted_years:
budget_amount = annual_data[country].get(year)
dataset['data'].append(float(budget_amount)
if budget_amount else None)
response_data['datasets'].append(dataset)
return response_data
......@@ -5,7 +5,7 @@ from flask import Blueprint, abort, jsonify, url_for
from compendium_v2.db import db
from compendium_v2.db.models import (DataEntryItem, DataEntrySection,
DataSourceType)
from compendium_v2.db.survey import get_budget_by_year
from compendium_v2.db.survey import get_budget_by_nren, get_budget_by_year
from compendium_v2.routes import common
routes = Blueprint('data-entry', __name__)
......@@ -131,11 +131,14 @@ def load_data(data_source_id: DataSourceType) -> Any:
response_data = {}
if data_source_id == DataSourceType.BUDGETS_BY_YEAR:
response_data = get_budget_by_year()
if data_source_id == DataSourceType.BUDGETS_BY_NREN:
response_data = get_budget_by_nren()
# Enrich response data
# Add the colour formatting
for index, dataset in enumerate(response_data['datasets']):
dataset['backgroundColor'] = col_pal[index % len(col_pal)]
dataset['borderColor'] = col_pal[index % len(col_pal)]
return response_data
......
......@@ -3,7 +3,7 @@ envlist = py39
[flake8]
exclude = ./.tox,./webapp,./compendium_v2/migrations
exclude = ./.tox,./webapp,./compendium_v2/migrations,./setup.py
[testenv]
deps =
......
import React, {ReactElement} from 'react';
import AnnualReport from './AnnualReport';
import DataAnalysis from './DataAnalysis';
import AnnualReport from './pages/AnnualReport';
import DataAnalysis from './pages/DataAnalysis';
import Navigation from './Navigation';
import About from './About';
import About from './pages/About';
import {BrowserRouter as Router,Switch, Route} from 'react-router-dom'
......
......@@ -26,6 +26,15 @@ export interface BudgetMatrix{
title:string
}
export interface DataEntrySection {
name: string,
description: string,
items: {
id: number,
title: string,
url: string
}[]
}
export interface Service {
compendium_id: number,
......
import React, {ReactElement, useEffect, useState} from 'react';
import {Bar} from 'react-chartjs-2';
import {DataSetProps} from './types';
import {
Chart as ChartJS,
CategoryScale,
LinearScale,
BarElement,
Title,
Tooltip,
Legend,
} from 'chart.js';
ChartJS.register(
CategoryScale,
LinearScale,
BarElement,
Title,
Tooltip,
Legend
);
function BarGraph(data: DataSetProps): ReactElement {
return (
<Bar data={data.data} options={{}}/>
);
}
export default BarGraph;
import React, {ReactElement, useEffect, useState} from 'react';
import {Line} from 'react-chartjs-2';
import {DataSetProps} from './types';
import {
Chart as ChartJS,
CategoryScale,
LinearScale,
PointElement,
LineElement,
Title,
Tooltip,
Legend,
} from 'chart.js';
ChartJS.register(
CategoryScale,
LinearScale,
PointElement,
LineElement,
Title,
Tooltip,
Legend
);
const options = {
responsive: true,
plugins: {
legend: {
position: 'top' as const,
},
title: {
display: true,
text: '',
},
},
};
function LineGraph(data: DataSetProps): ReactElement {
return (
<Line data={data.data} options={options}/>
);
}
export default LineGraph;
export type DatasetEntry = {
backgroundColor: string,
borderColor?: string,
data: (number|null)[],
label: string
}
export type DataSetProps = {
data: {
datasets: DatasetEntry[],
labels: string[]
}
}
File moved
import React, {useState, useEffect, ReactElement} from 'react';
import {Nren, Service, ServiceMatrix} from "./Schema";
import {Nren, Service, ServiceMatrix} from "../Schema";
// const api_url = window.location.origin;
......
import React, {ReactElement, useEffect, useState} from 'react';
import {Accordion, Col, Container, Form, Row} from "react-bootstrap";
import {Bar} from 'react-chartjs-2';
import {
Chart as ChartJS,
CategoryScale,
LinearScale,
BarElement,
Title,
Tooltip,
Legend,
} from 'chart.js';
import {BudgetMatrix} from "./Schema";
ChartJS.register(
CategoryScale,
LinearScale,
BarElement,
Title,
Tooltip,
Legend
);
import {Accordion, Col, Container, Form, ListGroup, Row} from "react-bootstrap";
import BarGraph from "../components/graphing/BarGraph";
import LineGraph from "../components/graphing/LineGraph";
import {DataSetProps} from "../components/graphing/types";
import {BudgetMatrix, DataEntrySection} from "../Schema";
export const options = {
// indexAxis: 'y' as const,
......@@ -42,7 +26,6 @@ function DataAnalysis(): ReactElement {
function api<T>(url: string, options: RequestInit): Promise<T> {
return fetch(url, options)
.then((response) => {
console.log(response)
if (!response.ok) {
return response.text().then((message) => {
console.error(`Failed to load datax: ${message}`, response.status);
......@@ -55,12 +38,31 @@ function DataAnalysis(): ReactElement {
}
const [budgetResponse, setBudgetMatrix] = useState<BudgetMatrix>();
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);
})
}
const loadData = () => {
api<BudgetMatrix>('/api/data-entries/item/2',{
if (selectedDataEntry == 0) {
getDataEntries();
return;
}
api<BudgetMatrix>('/api/data-entries/item/' + selectedDataEntry,{
referrerPolicy: "unsafe-url",
headers: {
"Access-Control-Allow-Origin": "*",
......@@ -68,15 +70,21 @@ function DataAnalysis(): ReactElement {
}
})
.then((budgetMatrix :BudgetMatrix)=>{
console.log('got response==>data');
console.log(budgetMatrix.data);
options.plugins.title.text = budgetMatrix.title;
setBudgetMatrix(budgetMatrix)
})
}
loadData()
}, []);
}, [selectedDataEntry]);
const renderEntries = () => {
return (
<ul>
</ul>
);
};
const empty_bar_response = {
'data': {
'datasets': [
......@@ -94,6 +102,25 @@ function DataAnalysis(): ReactElement {
}
const barResponse: BudgetMatrix = budgetResponse !== undefined ? budgetResponse : empty_bar_response;
const data: DataSetProps = {
data: {
datasets: [
{
backgroundColor: '#114466',
borderColor: '#114466',
data: [10, 13, 18,21,16,12],
label: '2016'
},
{
backgroundColor: '#FF4466',
borderColor: '#FF4466',
data: [15, null, 13,11,11,44],
label: '2017'
},
],
labels: ['AB', 'BC', 'CD', 'DE', 'EF', 'FE']
}
}
return (
<div>
......@@ -102,8 +129,10 @@ const barResponse: BudgetMatrix = budgetResponse !== undefined ? budgetResponse
<Row>
<Col>
<Row>
<Bar data={barResponse.data} options={options}/>
<BarGraph data={barResponse.data} />
</Row>
<Row>
<LineGraph data={barResponse.data} />
</Row>
<Row>{budgetResponse?.description}</Row>
......@@ -111,20 +140,15 @@ const barResponse: BudgetMatrix = budgetResponse !== undefined ? budgetResponse
<Col xs={3}>
<Accordion defaultActiveKey="0">
<Accordion.Item eventKey="0">
<Accordion.Header>NRENs</Accordion.Header>
<Accordion.Header>Items</Accordion.Header>
<Accordion.Body>
<Form>
<Form.Check
type={'checkbox'}
label='JISC'/>
<Form.Check
type={'checkbox'}
label='SURF'/>
<Form.Check
type={'checkbox'}
label='URAN'/>
</Form>
<ListGroup>
{
dataEntrySection?.items.map((item) => (
<ListGroup.Item key={item.id} action onClick={() => setSelectedDataEntry(item.id)}>{item.title}</ListGroup.Item>
))
}
</ListGroup>
</Accordion.Body>
</Accordion.Item>
</Accordion>
......
table {
min-width: 650;
min-width: 650px;
}
thead {
......
......@@ -80,7 +80,11 @@ const config: webpack.Configuration = {
compress: true,
port: 4000,
// Allow SPA urls to work with dev-server
historyApiFallback: true
historyApiFallback: true,
proxy: {
'/api': 'http://127.0.0.1:5000'
}
},
plugins: [
new ForkTsCheckerWebpackPlugin({
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment