395 lines
12 KiB
Vue
395 lines
12 KiB
Vue
<template>
|
|
<v-app id="inspire">
|
|
|
|
<v-app-bar app clipped-left>
|
|
<v-app-bar-nav-icon @click.stop="drawer = !drawer"/>
|
|
<v-toolbar-title>{{title}}</v-toolbar-title>
|
|
</v-app-bar>
|
|
|
|
<v-navigation-drawer
|
|
v-model="drawer"
|
|
app
|
|
clipped
|
|
stateless
|
|
>
|
|
|
|
<ChartConfig
|
|
:configs="chartConfig"
|
|
:configLists="configLists"
|
|
/>
|
|
|
|
<CountriesConfig
|
|
:lists="countriesLists"
|
|
:configs="countriesConfig"
|
|
/>
|
|
|
|
<DataLoader
|
|
v-model="allData"
|
|
></DataLoader>
|
|
|
|
</v-navigation-drawer>
|
|
|
|
<v-content>
|
|
<v-container class="sfill-height" fluid>
|
|
<v-row align="center" justify="center" id="maincontent" ref="maincontent">
|
|
<v-col class="shrink">
|
|
|
|
<MultiCountryChart
|
|
:countries="countriesData"
|
|
:scaleType="chartConfig.scaleSelected"
|
|
:dataType="chartConfig.typeSelected"
|
|
:width="chartWidth"
|
|
:height="chartWidth * (2/3)"
|
|
:since="syncSinceInt"
|
|
/>
|
|
|
|
<MultiBarChartSingle
|
|
:countriesData="countriesData"
|
|
:scaleType="chartConfig.scaleSelected"
|
|
:width="260"
|
|
:height="100"
|
|
/>
|
|
|
|
</v-col>
|
|
</v-row>
|
|
</v-container>
|
|
</v-content>
|
|
|
|
<v-footer app>
|
|
<span>
|
|
Using <a href="https://vuejs.org/" target="_blank">Vuejs</a>
|
|
and <a href="https://d3js.org/" target="_blank">d3</a> -
|
|
<a href="https://github.com/pomber/covid19" target="_blank">Data source</a>
|
|
</span>
|
|
</v-footer>
|
|
</v-app>
|
|
</template>
|
|
|
|
<script>
|
|
|
|
//import AllData from '../data/timeseries.json'
|
|
import ChartConfig from './components/ChartConfig'
|
|
import CountriesConfig from './components/CountriesConfig'
|
|
import MultiCountryChart from './components/d3/MultiCountryChart'
|
|
import MultiBarChartSingle from './components/MultiBarChartSingle'
|
|
import DataLoader from './components/DataLoader'
|
|
|
|
|
|
MultiBarChartSingle
|
|
|
|
let limit = (value, min, max) => {
|
|
return value > max ? max : value < min ? min : value
|
|
}
|
|
limit()
|
|
|
|
function formatDate(date) {
|
|
var d = new Date(date),
|
|
month = '' + (d.getMonth() + 1),
|
|
day = '' + d.getDate(),
|
|
year = d.getFullYear();
|
|
|
|
if (month.length < 2)
|
|
month = '0' + month;
|
|
if (day.length < 2)
|
|
day = '0' + day;
|
|
|
|
return [year, month, day].join('-');
|
|
}
|
|
|
|
|
|
const defaultUserConfig = {
|
|
chartConfig: {
|
|
typeSelected: 'confirmed',
|
|
scaleSelected: 'linear',
|
|
dayStart: 30,
|
|
dayEnd: 0,
|
|
ymax: 0,
|
|
syncSince: 0,
|
|
resetConfig: false,
|
|
math: {
|
|
exp: {
|
|
display: false,
|
|
factor: 6.5,
|
|
dayPrev: 26
|
|
},
|
|
pow: {
|
|
display: false,
|
|
factor: 3,
|
|
dayPrev: 5
|
|
},
|
|
logi: {
|
|
display: false,
|
|
factor:0.15,
|
|
dayPrev: 42
|
|
},
|
|
},
|
|
},
|
|
countriesConfig: {
|
|
selected: [{name: "France"}, {name: "Italy"},{name:"Spain"},{name:"United Kingdom"}],
|
|
basePalette: "tolRainbowColor"
|
|
},
|
|
}
|
|
defaultUserConfig
|
|
|
|
export default {
|
|
name: 'AppVuetify',
|
|
components: {ChartConfig, CountriesConfig, MultiCountryChart, MultiBarChartSingle, DataLoader},
|
|
props: {
|
|
source: String,
|
|
},
|
|
data () { return {
|
|
drawer: true,
|
|
title: 'Cov Charts',
|
|
allData: {},
|
|
chartWidth: 500,
|
|
chartConfig: Object.assign({},defaultUserConfig.chartConfig),
|
|
countriesConfig:Object.assign({}, defaultUserConfig.countriesConfig),
|
|
configLists: {
|
|
scaleList: ['log', 'linear'],
|
|
typesList: ['confirmed', 'deaths', 'recovered'],
|
|
},
|
|
countriesLists: {
|
|
top: ['Belgium', 'Sweden', 'France', 'Japan', 'Korea, South', 'Russia', 'Germany', 'Spain', 'Italy', 'China', 'United Kingdom', 'US'],
|
|
// Todo list of all countries using localstorage
|
|
//all: Object.keys(AllData).sort(),
|
|
},
|
|
debug: false,
|
|
}},
|
|
watch: {
|
|
drawer() {
|
|
console.log('dd', this.drawer)
|
|
},
|
|
countriesConfig: {
|
|
handler: function () {
|
|
this.saveConfig()
|
|
},
|
|
deep: true
|
|
},
|
|
chartConfig: {
|
|
handler: function (val) {
|
|
val.resetConfig ? this.resetConfig() : this.saveConfig()
|
|
},
|
|
deep: true
|
|
},
|
|
},
|
|
created() {
|
|
this.$vuetify.theme.dark = true
|
|
},
|
|
mounted() {
|
|
// Todo find a better way to get size
|
|
//this.chartWidth = this.$refs.maincontent.clientWidth * 2 / 3
|
|
this.chartWidth = 800
|
|
this.loadConfig()
|
|
|
|
},
|
|
methods: {
|
|
loadConfig() {
|
|
let uc = localStorage.getItem('userConfig')
|
|
if (uc && uc !== 'null') {
|
|
uc = JSON.parse(uc)
|
|
this.countriesConfig = uc.countriesConfig
|
|
this.chartConfig = uc.chartConfig
|
|
} else {
|
|
this.countriesConfig = Object.assign({},defaultUserConfig.countriesConfig)
|
|
this.chartConfig = Object.assign({},defaultUserConfig.chartConfig)
|
|
}
|
|
},
|
|
saveConfig() {
|
|
let userConfig = {countriesConfig: this.countriesConfig, chartConfig: this.chartConfig}
|
|
localStorage.setItem('userConfig', JSON.stringify(userConfig))
|
|
},
|
|
resetConfig() {
|
|
localStorage.setItem('userConfig', 'null')
|
|
this.loadConfig()
|
|
},
|
|
ccc(a, b) {
|
|
if (this.debug) console.log(a, b?b:'')
|
|
},
|
|
|
|
addMathFunctions(cdata, ymax) {
|
|
let cc = this.chartConfig.math
|
|
// Pow
|
|
if (cc.pow.display) {
|
|
cdata.push(this.calcFunction(cdata,
|
|
'pow3',
|
|
'#ffc500',
|
|
i => limit(Math.pow(i - parseInt(cc.pow.dayPrev), parseFloat(cc.pow.factor)), 1, ymax)
|
|
))
|
|
}
|
|
|
|
// Exp
|
|
if (cc.exp.display) {
|
|
cdata.push(this.calcFunction(cdata,
|
|
'exp',
|
|
'#fd1e00',
|
|
i => limit(Math.exp((i + parseInt(cc.exp.dayPrev)) / parseFloat(cc.exp.factor)), 1, ymax)
|
|
))
|
|
}
|
|
|
|
|
|
if (cc.logi.display) {
|
|
// Logistic function
|
|
let L = ymax
|
|
let k = parseFloat(cc.logi.factor)
|
|
let xo = parseInt(cc.logi.dayPrev)
|
|
cdata.push(this.calcFunction(cdata,
|
|
'logistic',
|
|
'rgb(253,130,0)',
|
|
//i => limit(L / (1 + Math.exp(Math.E, -k*(i - xo))), 1, 60000)
|
|
i => limit(L / (1 + Math.exp(-k * (i - xo))), 0, ymax)
|
|
))
|
|
}
|
|
|
|
return cdata
|
|
|
|
},
|
|
calcFunction(cdata, name, color, callback) {
|
|
return {
|
|
country: name,
|
|
color: color,
|
|
isMaths: true,
|
|
list: cdata[0].list.map((day, id) => {
|
|
return {
|
|
confirmed: callback(id),
|
|
date: day.date
|
|
}
|
|
})
|
|
}
|
|
|
|
},
|
|
calculateLog(cdata, ymax) {
|
|
|
|
console.log(' calculateLog',)
|
|
let max = ymax
|
|
let dayDecal = 2
|
|
|
|
let pointsCount = cdata[0].list.length
|
|
let e = Math.log(max)
|
|
let step = e / pointsCount
|
|
|
|
step , dayDecal;
|
|
let getY = i => {
|
|
i = i === 0 ? 0.5 : i
|
|
let value = Math.exp((i + dayDecal) * step)
|
|
//let value = Math.pow((i), 3)
|
|
|
|
return value < max ? value : undefined
|
|
}
|
|
let pointList = cdata[0].list.map((d, i) => {
|
|
return {
|
|
confirmed: getY(i),
|
|
date: d.date
|
|
}
|
|
})
|
|
let log = {
|
|
country: "log",
|
|
color: '#FFCC00',
|
|
list: pointList
|
|
}
|
|
cdata.push(log)
|
|
|
|
return cdata
|
|
},
|
|
|
|
},
|
|
computed: {
|
|
syncSinceInt() {
|
|
return parseInt(this.chartConfig.syncSince)
|
|
},
|
|
countriesData() {
|
|
|
|
if (!('France' in this.allData)) {
|
|
return []
|
|
}
|
|
|
|
let formatedData = []
|
|
const addSomeDays = false
|
|
let ymax = 0
|
|
|
|
this.countriesConfig.selected.map((country) => {
|
|
|
|
let filtered = JSON.parse(JSON.stringify(this.allData[country.name]))
|
|
|
|
if (this.syncSinceInt !== 0) {
|
|
filtered = filtered.filter((e) => {
|
|
//console.log(' e', e[this.chartConfig.typeSelected], syncSince, typeof this.syncSince)
|
|
return e[this.chartConfig.typeSelected] >= this.syncSinceInt
|
|
}
|
|
)
|
|
}
|
|
|
|
if (this.chartConfig.ymax > 0) {
|
|
filtered = filtered.map((d) => {
|
|
this.configLists.typesList.map(e => {
|
|
d[e] = d[e] > this.chartConfig.ymax ? NaN : d[e]
|
|
})
|
|
return d
|
|
})
|
|
}
|
|
|
|
filtered = filtered.filter((e, i) =>
|
|
i >= (this.chartConfig.dayStart - 1) &&
|
|
i < (filtered.length - this.chartConfig.dayEnd)
|
|
)
|
|
|
|
if (addSomeDays) {
|
|
// Adding addDays data points
|
|
let datebase = new Date(filtered.slice(-1)[0].date)
|
|
for (let i = 0; i < this.chartConfig.addDays; i++) {
|
|
let dd = new Date(datebase.getFullYear(), datebase.getMonth(), datebase.getDate() + i);
|
|
filtered.push({
|
|
date: formatDate(dd),
|
|
deaths: undefined,
|
|
recovered: undefined,
|
|
confirmed: undefined,
|
|
})
|
|
}
|
|
}
|
|
|
|
// Get ymax
|
|
|
|
filtered.map(d => {
|
|
ymax = d[this.chartConfig.typeSelected] > ymax ? d[this.chartConfig.typeSelected] : ymax
|
|
})
|
|
|
|
country.list = filtered
|
|
formatedData.push(country)
|
|
})
|
|
|
|
|
|
formatedData = this.addMathFunctions(formatedData, ymax)
|
|
|
|
return formatedData
|
|
}
|
|
}
|
|
}
|
|
</script>
|
|
<style>
|
|
|
|
html, body {
|
|
height: 100%;
|
|
margin: 0;
|
|
font-size: 12px;
|
|
}
|
|
|
|
input, select, .v-input, .v-label {
|
|
font-size: 13px;
|
|
}
|
|
|
|
.v-list--dense .v-list-item {
|
|
min-height: 20px;
|
|
}
|
|
|
|
.v-list--dense .v-list-item .v-list-item__content {
|
|
padding: 2px 0;
|
|
}
|
|
|
|
.v-list-item__action{
|
|
margin: 8px 0;
|
|
}
|
|
.v-text-field.v-text-field--solo .v-input__control {
|
|
min-height: 20px;
|
|
}
|
|
|
|
</style>
|