Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Next Next commit
feat: Added non-interactive chart for history per country
  • Loading branch information
jkga committed Apr 10, 2020
commit 5453dc3b326dd0f60dba471998ec628fd7ebbb31
3 changes: 2 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
node_modules
.now
.now
.DS_Store
26 changes: 26 additions & 0 deletions app.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ const express = require('express'),
util = require('./bin/util'),
axios = require('axios'),
covid19 = require('./lib/cli'),
covid19GFX = require ('./lib/cli/gfx'),
pkg = require('./package.json'), // package.json info
apiBaseURL = "https://corona.lmao.ninja", // NovelCOVID API
port = process.env.port || 7070; // set port
Expand Down Expand Up @@ -128,6 +129,31 @@ app.get('/history/:country/:chartType(cases|deaths)?', async (req, res, next) =>
return next();
});


// historical chart by country
app.get('/history/charts/:country', async (req, res, next) => {
const userAgent = req.headers['user-agent'],
countryData = req.params.country,
chartType = req.params.chartType || 'cases',
summary = await axios.get(`${apiBaseURL}/countries/${countryData}`),
history = await axios.get(`${apiBaseURL}/v2/historical/${summary.data.country}?lastdays=all`),
s = summary.data,
h = history.data;

if (util.isCommandline(userAgent)) {
covid19GFX.historyCountryTracker(
req, res,
s.country, s.cases, s.todayCases,
s.deaths, s.todayDeaths, s.recovered,
s.active, s.critical, s.casesPerOneMillion,
s.updated, h, chartType, s.countryInfo
)

return null;
}
return next();
});

app.get('*', (req, res) => res.send(`
Welcome to COVID-19 Tracker CLI v${pkg.version} by Waren Gonzaga\n
Please visit: https://warengonza.ga/covid19-tracker-cli
Expand Down
152 changes: 152 additions & 0 deletions lib/cli/gfx/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,152 @@
const
blessed = require('blessed'),
fs = require('fs'),
contrib = require('blessed-contrib'),
present = require('./presenter'),
pkg = require('../../../package.json'),
xml2js = require('xml2js'),
say = require('../../sayings/threads.json'),
ts = Date.now(),
date_ob = new Date(ts),
date = date_ob.getDate(),
month = date_ob.getMonth() + 1,
year = date_ob.getFullYear(),
currentdate = month + "/" + date + "/" + year,
space = ' ',
br = '\n',
header = '`COVID19-TRACKER-CLI (v'+pkg.version+')`',
tagline = '*A curl-based command line tracker for Novel Coronavirus or COVID-19 pandemic.*',
source = 'Source: https://www.worldometers.info/coronavirus/',
repo = 'Code: https://github.com/warengonzaga/covid19-tracker-cli',
bmcurl = 'warengonza.ga/coffee4dev',
twitterhandle = '@warengonzaga',
twitterhashtag = '#covid19trackercli',
gcashNum = '+639176462753',
ansiBMC = '`(Buy Me A Coffee)` '+bmcurl,
ansiTwitter = twitterhandle+space+twitterhashtag,
ansiGCash = '(GCash) '+gcashNum,
XMLTemplate = '/template.xml';

let cachedTemplate = null

// random sayings
const randomSay = () => {
let random = Math.floor(Math.random() * say.length);
return say[random];
};

const patchBlessed = () => {
blessed.Program.prototype.listem = function() {}
process.on = function() {}
};

patchBlessed();


exports.historyCountryTracker = (req, res, n, c, tC, d, tD, r, a, cl, cPOM, u, h, chartType, countryInfo) => {
const name = n, cases = c, todayCases = tC,
deaths = d, todayDeaths = tD, recovered = r,
active = a, critical = cl, casesPerOneMillion = cPOM,
mortalityPercentage = (d/c)*100, recoveredPercentage = (r/c)*100,
asof = new Date(u),
dates = Object.keys(h.timeline[chartType]),
from = dates[0],
to = dates[dates.length - 1],
parser = new xml2js.Parser(),
builder = new xml2js.Builder(),
tableFooter = randomSay(),
defaultfooter = ansiBMC+ansiTwitter+br+br,
specialfooter = ansiGCash+br+ansiBMC+ansiTwitter+br+br;

const parseXML = (data) => {
parser.parseString(data, function (err, result) {

const casesDataTable =
'\n' +
` ,${cases},${deaths},${recovered},${active},${casesPerOneMillion}\n` +
' ,Today Cases,Today Deaths,Critical,Mortality %,Recovery %\n' +
` ,${todayCases},${todayDeaths},${critical},${mortalityPercentage},${recoveredPercentage}\n`;

// header
result.document.page[0].item[0].markdown[0].markdown[0] = header+br+tagline

// map
result.document.page[0].item[1].map[0].markers[0].m[0].$ = {
lat: countryInfo.lat,
lon: countryInfo.long,
char: '\u24E7'+` ${name}`,
color: 'magenta',
};

// Doughnut/donut
result.document.page[0].item[4].donut[0].data[0].m[0].$={
color: 'red',
percent: parseFloat(mortalityPercentage).toFixed(2),
label: 'Mortality',
};

result.document.page[0].item[4].donut[0].data[0].m[1].$={
color: 'green',
percent: parseFloat(recoveredPercentage).toFixed(2),
label: 'Recovery',
};

// bar graph
result.document.page[0].item[3].bar[0].$['data-data'] = `${cases},${deaths},${recovered},${active}`;

// line graph
result.document.page[0].item[5].line[0].label = `Cases from ${from} to ${to}`
result.document.page[0].item[5].line[0].data[0].m[0].$ = {
title: 'Cases',
'style-line': 'blue',
x:Object.keys(h.timeline.cases).join(','),
y:Object.values(h.timeline.cases).join(','),
};

result.document.page[0].item[5].line[0].data[0].m[1].$ = {
title: 'Deaths',
'style-line': 'red',
x:Object.keys(h.timeline.deaths).join(','),
y:Object.values(h.timeline.deaths).join(','),
};

result.document.page[0].item[5].line[0].data[0].m[2].$ = {
title: 'Recovered',
'style-line': 'green',
x:Object.keys(h.timeline.recovered).join(','),
y:Object.values(h.timeline.recovered).join(','),
};

// Historical data table
result.document.page[0].item[2].table[0].label = 'Historical data as of '+asof.toLocaleString()+' [Date:'+currentdate+']'
result.document.page[0].item[2].table[0]['data-data'][0] = casesDataTable;

// footer
result.document.page[0].item[6].markdown[0].markdown[0] = (n.toLowerCase() == 'philippines') ? tableFooter+br+specialfooter+br+source+br+repo : tableFooter+br+defaultfooter+br+source+br+repo;

present(req, res, builder.buildObject(result), function(err) {
if (err) console.log(new Error().stack);
if (err) return contrib.serverError(req, res, err);
});

});
}

const readXML = () => {
fs.readFile(__dirname + XMLTemplate, (err, data) => {
cachedTemplate = data
parseXML(data)
});
}

// read XML template from file or cache
if(!cachedTemplate) return readXML ()

try {
return parseXML(new Buffer.from(cachedTemplate.toString()))
} catch (e) {
return readXML ()
}


};
57 changes: 57 additions & 0 deletions lib/cli/gfx/presenter.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
const parse = require('xml2js').parseString,
Viewer = require('wopr/lib/document-viewer'),
blessed = require('blessed'),
contrib = require('blessed-contrib');


const present = (req, res, body, cba) => {

blessed.Screen.global = null
blessed.Program.global = null

if (!body || body=="") {
return cba("You must upload the document to present as the POST body")
}

parse(body, function (err, doc) {
try {

if (err) {
return cba("Document xml is not valid: " + err)
}

if (!doc || !doc.document) return cba("document not valid or has no pages")
if (!doc.document.page || doc.document.page.length==0) return cba("document must have at least one page")

var screen = contrib.createScreen(req, res)
if (screen==null) return

viewer = new Viewer(doc.document, screen)
var err = viewer.renderPage(0, '\u2800')
if (err!==null) {
clean(screen)
return cba(err)
}

//note the setTimeout is necessary even if delay is 0
setTimeout(function() {
//restore cursor
res.end('\033[?25h')
clean(screen)
return cba()
}, '' ? 5000:0)
}

catch (e) {
return cba(e)
}
})
}

function clean(screen) {
//TODO this code is very sensitive to blessed versions, need to check right version/usage
//screen.program.destroy()
//screen.destroy()
}

module.exports = present
59 changes: 59 additions & 0 deletions lib/cli/gfx/template.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@

<document>
<page>
<item col="0" row="0" colSpan="2" rowSpan="2">
<markdown style-paragraph="chalk.white" style-strong="chalk.cyan.underline" style-em="chalk.green" border-type="line" border-fg="gray">
<markdown></markdown>
</markdown>
</item>

<item col="0" row="2" colSpan="4" rowSpan="3">
<map style-shapeColor="yellow">
<markers>
<m lat="37.5000" lon="-79.0000" color="magenta" char='-x-'/>
</markers>
</map>
</item>

<item col="2" row="0" colSpan="7" rowSpan="2">
<table fg="white" width="30%" height="30%" border-type="line" border-fg="gray" columnSpacing="5" columnWidth="1,15,15,15,15,15" data-headers=",Cases,Deaths,Recovered, Active,Cases/Million" interactive="false" label="Historical chart as of 4/1/2020, 9:15:56 AM Date: [4/1/2020]">
<data-data>
,1,02:17,21,123,3453
,Today Cases,Today Deaths,Critical,Mortality %,Recovery %
,1,02:17,21,123,3453
</data-data>
</table>
</item>

<item col="4" row="2" colSpan="3" rowSpan="3">
<bar barWidth="9" barSpacing="3" xOffset="1" maxHeight="5" height="100%" border-type="line" border-fg="gray" data-titles="Cases,Deaths,Recovered,Active" data-data="2,4,2,5" />
</item>

<item col="7" row="2" colSpan="2" rowSpan="3">
<donut radius="8" archWidth='2' yPadding='4' border-type="line" border-fg="gray" label="Mortality Rate" xOffset='1' spacing='0'>
<data>
<m color="red" percent="40" label="mort" />
<m color="yellow" percent="40" label="recov" />
</data>
</donut>
</item>

<item col="0" row="5" colSpan="9" rowSpan="4">
<line xPadding="0" showLegend="true" legend-width="14" border-type="line" border-fg="gray" xLabelPadding='1' showNthLabel='4' label='History Timeline'>
<data>
<m title="Cases" style-line="red" x="" y=""/>
<m title="Deaths" style-line="red" x="" y=""/>
<m title="Recovered" style-line="green" x="" y=""/>
</data>
</line>
</item>

<item col="0" row="9" colSpan="9" rowSpan="2">
<markdown style-paragraph="chalk.white" style-strong="chalk.cyan.underline" style-em="chalk.green" border-type="line" border-fg="gray">
<markdown></markdown>
</markdown>
</item>
</page>


</document>
Loading