covid19-dashboard
Version:
Dashboard App displaying COVID-19 numbers by country
282 lines (243 loc) • 8.9 kB
JavaScript
import ComponentController from '../../../node_modules/neo.mjs/src/controller/Component.mjs';
import NeoArray from '../../../node_modules/neo.mjs/src/util/Array.mjs';
/**
* @class Covid.view.TableContainerController
* @extends Neo.controller.Component
*/
class TableContainerController extends ComponentController {
static getConfig() {return {
/**
* @member {String} className='Covid.view.TableContainerController'
* @protected
*/
className: 'Covid.view.TableContainerController',
/**
* @member {String} apiBaseUrl='https://disease.sh/'
*/
apiBaseUrl: 'https://disease.sh/',
/**
* @member {String} apiHistoricalDataEndpoint='v3/covid-19/historical/'
*/
apiHistoricalDataEndpoint: 'v3/covid-19/historical/',
/**
* Number of days you want the data to go back to. Default is 30. Use all for full data set. Ex: 15, all, 24
* @member {Number|String} apiHistoricalDataTimeRange='all'
*/
apiHistoricalDataTimeRange: 'all',
/**
* Remove all records with 0 cases from the historical data (table & chart)
* @member {Boolean} removeEmptyRecords=true
*/
removeEmptyRecords: true,
/**
* @member {Object} selectedRecord=null
*/
selectedRecord: null,
/**
* @member {Neo.table.Container|null} table_=null
* @protected
*/
table_: null
}}
/**
* @param {Object} data
*/
addStoreItems(data) {
let me = this,
dataArray = [],
map = {},
timeline = data?.timeline,
nextItem;
// https://github.com/NovelCOVID/API/issues/309 // different format for 'all'
if (data && !data.timeline) {
timeline = data;
}
if (timeline) {
Object.entries(timeline.cases || {}).forEach(([key, value]) => {
map[key] = {date: new Date(key).toISOString(), cases: value};
});
Object.entries(timeline.deaths || {}).forEach(([key, value]) => {
if (map.hasOwnProperty(key)) {
map[key].deaths = value;
} else {
map[key] = {date: new Date(key).toISOString(), deaths: value};
}
});
Object.entries(timeline.recovered || {}).forEach(([key, value]) => {
if (map.hasOwnProperty(key)) {
map[key].recovered = value;
} else {
map[key] = {date: new Date(key).toISOString(), recovered: value};
}
});
Object.entries(map).forEach(([key, value]) => {
value.active = value.cases - value.deaths - value.recovered;
dataArray.push(value);
});
if (me.removeEmptyRecords) {
[...dataArray].forEach(item => {
if (item.cases === 0) {
NeoArray.remove(dataArray, item);
}
});
}
// the array is sorted by date ASC
Object.assign(dataArray[0], {
dailyActive : dataArray[0].active,
dailyCases : dataArray[0].cases,
dailyDeaths : dataArray[0].deaths,
dailyRecovered: dataArray[0].recovered
});
dataArray.forEach((item, index) => {
nextItem = dataArray[index + 1];
if (nextItem) {
Object.assign(nextItem, {
dailyActive : nextItem.active - item.active,
dailyCases : nextItem.cases - item.cases,
dailyDeaths : nextItem.deaths - item.deaths,
dailyRecovered: nextItem.recovered - item.recovered
});
}
});
// todo: we could only update the active tab
me.getReference('historical-data-table').store.data = dataArray;
me.updateLineChart(dataArray);
}
}
/**
* @param {Object} record
* @protected
* @returns {Object}
*/
static assignFieldsOrNull(record) {
return {
active : record.active || null,
cases : record.cases || null,
deaths : record.deaths || null,
dailyActive : record.dailyActive || null,
dailyCases : record.dailyCases || null,
dailyDeaths : record.dailyDeaths || null,
dailyRecovered: record.dailyRecovered || null,
recovered : record.recovered || null
};
}
/**
* Triggered when accessing the table config
* @param {Neo.table.Container|null} value
* @protected
*/
beforeGetTable(value) {
if (!value) {
this._table = value = this.getReference('table');
}
return value;
}
/**
* @param {String} countryName
*/
loadHistoricalData(countryName) {
let me = this,
apiPath = me.apiBaseUrl + me.apiHistoricalDataEndpoint + countryName + '?lastdays=' + me.apiHistoricalDataTimeRange;
fetch(apiPath)
.then(response => response.json())
.catch(err => console.log('Can’t access ' + apiPath, err))
.then(data => me.addStoreItems(data));
}
/**
* {Object} data
*/
on520pxButtonClick(data) {
this.getReference('controls-panel').width = 520;
}
/**
* {Object} data
*/
on800pxButtonClick(data) {
this.getReference('controls-panel').width = 800;
}
/**
* {Object} data
*/
onCollapseButtonClick(data) {
let panel = this.getReference('controls-panel'),
expand = panel.width === 40;
panel.width = expand ? this.component.historyPanelWidth : 40;
data.component.text = expand ? 'X' : '+';
}
/**
* {Object} record
*/
onCountryChange(record) {
let me = this;
if (record) {
me.selectedRecord = {...record};
} else {
me.selectedRecord = null;
}
// removed optional chaining for now, see: https://github.com/neomjs/neo/issues/467
me.loadHistoricalData(record?.countryInfo?.iso2 || 'all');
me.getReference('historical-data-label').html = 'Historical Data (' + (record?.country || 'World') + ')';
}
/**
* {Object} data
*/
onDailyValuesChange(data) {
let chartId = this.getReference('line-chart').id,
logCheckbox = this.getReference('logarithmic-scale-checkbox'),
value = data.value;
if (value) {
logCheckbox.set({
checked : false,
disabled: data.value
});
} else {
logCheckbox.disabled = false;
}
Neo.main.addon.AmCharts.setProperties({
id : chartId,
properties: {
'series.values.0.dataFields.valueY' : value ? 'dailyActive' : 'active',
'series.values.1.dataFields.valueY' : value ? 'dailyCases' : 'cases',
'series.values.2.dataFields.valueY' : value ? 'dailyDeaths' : 'deaths',
'series.values.3.dataFields.valueY' : value ? 'dailyRecovered' : 'recovered'
}
});
Neo.main.addon.AmCharts.callMethod({
id : chartId,
path: 'invalidateData'
});
}
/**
* {Object} data
*/
onLogarithmicScaleChange(data) {
Neo.main.addon.AmCharts.setProperty({
id : this.getReference('line-chart').id,
path : 'yAxes.values.0.logarithmic',
value: data.value
});
}
/**
* Logarithmic Axis break for values of 0, so we need to change those to null
* Adding the current record, since the historical data starts "yesterday"
* @param {Object[]} dataArray
*/
updateLineChart(dataArray) {
let me = this,
record = me.selectedRecord,
chart = me.getReference('line-chart');
dataArray.forEach(item => Object.assign(item, TableContainerController.assignFieldsOrNull(item)));
if (!record) {
record = me.getParent().summaryData;
}
if (record) {
dataArray.push({
date: new Date().toISOString(),
...TableContainerController.assignFieldsOrNull(record)
});
}
chart.chartData = dataArray;
}
}
Neo.applyClassConfig(TableContainerController);
export {TableContainerController as default};