tooltip styles, code cleanup, case doubling
This commit is contained in:
parent
a714af2c1d
commit
f5ba23cc75
236
generate.js
236
generate.js
@ -239,6 +239,27 @@ const processRecords = async () => {
|
|||||||
return calcGrowthRate(ts, ts.length - 1, 7);
|
return calcGrowthRate(ts, ts.length - 1, 7);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const convertTsObjectToArray = (obj) => {
|
||||||
|
return Object.keys(obj).sort().map((date) => {
|
||||||
|
return {
|
||||||
|
key: date,
|
||||||
|
value: obj[date].value,
|
||||||
|
delta: obj[date].delta,
|
||||||
|
};
|
||||||
|
})
|
||||||
|
};
|
||||||
|
|
||||||
|
const mergeTsArrayIntoObject = (source, target) => {
|
||||||
|
source.forEach((ts) => {
|
||||||
|
target[ts.key] = target[ts.key] || {
|
||||||
|
value: 0,
|
||||||
|
delta: 0,
|
||||||
|
};
|
||||||
|
target[ts.key].value += ts.value;
|
||||||
|
target[ts.key].delta += ts.delta;
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
const normalizeRecord = (record) => {
|
const normalizeRecord = (record) => {
|
||||||
record.timeSeriesDaily = [];
|
record.timeSeriesDaily = [];
|
||||||
record.timeSeriesMonthly = [];
|
record.timeSeriesMonthly = [];
|
||||||
@ -508,41 +529,11 @@ const processRecords = async () => {
|
|||||||
|
|
||||||
stateItem.total += record.total;
|
stateItem.total += record.total;
|
||||||
stateItem.cases.total += record.cases.total;
|
stateItem.cases.total += record.cases.total;
|
||||||
record.timeSeriesDaily.forEach((ts) => {
|
|
||||||
stateItem.timeSeriesDaily[ts.key] = stateItem.timeSeriesDaily[ts.key] || {
|
|
||||||
value: 0,
|
|
||||||
delta: 0,
|
|
||||||
};
|
|
||||||
stateItem.timeSeriesDaily[ts.key].value += ts.value;
|
|
||||||
stateItem.timeSeriesDaily[ts.key].delta += ts.delta;
|
|
||||||
});
|
|
||||||
|
|
||||||
record.timeSeriesMonthly.forEach((ts) => {
|
mergeTsArrayIntoObject(record.timeSeriesDaily, stateItem.timeSeriesDaily);
|
||||||
stateItem.timeSeriesMonthly[ts.key] = stateItem.timeSeriesMonthly[ts.key] || {
|
mergeTsArrayIntoObject(record.timeSeriesMonthly, stateItem.timeSeriesMonthly);
|
||||||
value: 0,
|
mergeTsArrayIntoObject(record.cases.timeSeriesDaily, stateItem.cases.timeSeriesDaily);
|
||||||
delta: 0,
|
mergeTsArrayIntoObject(record.cases.timeSeriesMonthly, stateItem.cases.timeSeriesMonthly);
|
||||||
};
|
|
||||||
stateItem.timeSeriesMonthly[ts.key].value += ts.value;
|
|
||||||
stateItem.timeSeriesMonthly[ts.key].delta += ts.delta;
|
|
||||||
});
|
|
||||||
|
|
||||||
record.cases.timeSeriesDaily.forEach((ts) => {
|
|
||||||
stateItem.cases.timeSeriesDaily[ts.key] = stateItem.cases.timeSeriesDaily[ts.key] || {
|
|
||||||
value: 0,
|
|
||||||
delta: 0,
|
|
||||||
};
|
|
||||||
stateItem.cases.timeSeriesDaily[ts.key].value += ts.value;
|
|
||||||
stateItem.cases.timeSeriesDaily[ts.key].delta += ts.delta;
|
|
||||||
});
|
|
||||||
|
|
||||||
record.cases.timeSeriesMonthly.forEach((ts) => {
|
|
||||||
stateItem.cases.timeSeriesMonthly[ts.key] = stateItem.cases.timeSeriesMonthly[ts.key] || {
|
|
||||||
value: 0,
|
|
||||||
delta: 0,
|
|
||||||
};
|
|
||||||
stateItem.cases.timeSeriesMonthly[ts.key].value += ts.value;
|
|
||||||
stateItem.cases.timeSeriesMonthly[ts.key].delta += ts.delta;
|
|
||||||
});
|
|
||||||
|
|
||||||
stateItem.counties.push(record);
|
stateItem.counties.push(record);
|
||||||
} else {
|
} else {
|
||||||
@ -556,41 +547,10 @@ const processRecords = async () => {
|
|||||||
item.total += record.total;
|
item.total += record.total;
|
||||||
item.cases.total += record.cases.total;
|
item.cases.total += record.cases.total;
|
||||||
|
|
||||||
record.timeSeriesDaily.forEach((ts) => {
|
mergeTsArrayIntoObject(record.timeSeriesDaily, item.timeSeriesDaily);
|
||||||
item.timeSeriesDaily[ts.key] = item.timeSeriesDaily[ts.key] || {
|
mergeTsArrayIntoObject(record.timeSeriesMonthly, item.timeSeriesMonthly);
|
||||||
value: 0,
|
mergeTsArrayIntoObject(record.cases.timeSeriesDaily, item.cases.timeSeriesDaily);
|
||||||
delta: 0,
|
mergeTsArrayIntoObject(record.cases.timeSeriesMonthly, item.cases.timeSeriesMonthly);
|
||||||
};
|
|
||||||
item.timeSeriesDaily[ts.key].value += ts.value;
|
|
||||||
item.timeSeriesDaily[ts.key].delta += ts.delta;
|
|
||||||
});
|
|
||||||
|
|
||||||
record.timeSeriesMonthly.forEach((ts) => {
|
|
||||||
item.timeSeriesMonthly[ts.key] = item.timeSeriesMonthly[ts.key] || {
|
|
||||||
value: 0,
|
|
||||||
delta: 0,
|
|
||||||
};
|
|
||||||
item.timeSeriesMonthly[ts.key].value += ts.value;
|
|
||||||
item.timeSeriesMonthly[ts.key].delta += ts.delta;
|
|
||||||
});
|
|
||||||
|
|
||||||
record.cases.timeSeriesDaily.forEach((ts) => {
|
|
||||||
item.cases.timeSeriesDaily[ts.key] = item.cases.timeSeriesDaily[ts.key] || {
|
|
||||||
value: 0,
|
|
||||||
delta: 0,
|
|
||||||
};
|
|
||||||
item.cases.timeSeriesDaily[ts.key].value += ts.value;
|
|
||||||
item.cases.timeSeriesDaily[ts.key].delta += ts.delta;
|
|
||||||
});
|
|
||||||
|
|
||||||
record.cases.timeSeriesMonthly.forEach((ts) => {
|
|
||||||
item.cases.timeSeriesMonthly[ts.key] = item.cases.timeSeriesMonthly[ts.key] || {
|
|
||||||
value: 0,
|
|
||||||
delta: 0,
|
|
||||||
};
|
|
||||||
item.cases.timeSeriesMonthly[ts.key].value += ts.value;
|
|
||||||
item.cases.timeSeriesMonthly[ts.key].delta += ts.delta;
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
|
|
||||||
Object.keys(perStateTotals).forEach((stateName) => {
|
Object.keys(perStateTotals).forEach((stateName) => {
|
||||||
@ -608,36 +568,12 @@ const processRecords = async () => {
|
|||||||
population: item.population,
|
population: item.population,
|
||||||
deathsPerMillion: item.population > 0 ? item.total / item.population * 1000000 : 0,
|
deathsPerMillion: item.population > 0 ? item.total / item.population * 1000000 : 0,
|
||||||
casesPerMillion: item.population > 0 ? item.cases.total / item.population * 1000000 : 0,
|
casesPerMillion: item.population > 0 ? item.cases.total / item.population * 1000000 : 0,
|
||||||
timeSeriesDaily: Object.keys(item.timeSeriesDaily).sort().map((date) => {
|
timeSeriesDaily: convertTsObjectToArray(item.timeSeriesDaily),
|
||||||
return {
|
timeSeriesMonthly: convertTsObjectToArray(item.timeSeriesMonthly),
|
||||||
key: date,
|
|
||||||
value: item.timeSeriesDaily[date].value,
|
|
||||||
delta: item.timeSeriesDaily[date].delta,
|
|
||||||
};
|
|
||||||
}),
|
|
||||||
timeSeriesMonthly: Object.keys(item.timeSeriesMonthly).sort().map((date) => {
|
|
||||||
return {
|
|
||||||
key: date,
|
|
||||||
value: item.timeSeriesMonthly[date].value,
|
|
||||||
delta: item.timeSeriesMonthly[date].delta,
|
|
||||||
};
|
|
||||||
}),
|
|
||||||
cases: {
|
cases: {
|
||||||
total: item.cases.total,
|
total: item.cases.total,
|
||||||
timeSeriesDaily: Object.keys(item.cases.timeSeriesDaily).sort().map((date) => {
|
timeSeriesDaily: convertTsObjectToArray(item.cases.timeSeriesDaily),
|
||||||
return {
|
timeSeriesMonthly: convertTsObjectToArray(item.cases.timeSeriesMonthly),
|
||||||
key: date,
|
|
||||||
value: item.cases.timeSeriesDaily[date].value,
|
|
||||||
delta: item.cases.timeSeriesDaily[date].delta,
|
|
||||||
};
|
|
||||||
}),
|
|
||||||
timeSeriesMonthly: Object.keys(item.cases.timeSeriesMonthly).sort().map((date) => {
|
|
||||||
return {
|
|
||||||
key: date,
|
|
||||||
value: item.cases.timeSeriesMonthly[date].value,
|
|
||||||
delta: item.cases.timeSeriesMonthly[date].delta,
|
|
||||||
};
|
|
||||||
}),
|
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -646,6 +582,7 @@ const processRecords = async () => {
|
|||||||
stateItem.rollingAverageDaily = getRollingAverage(stateItem);
|
stateItem.rollingAverageDaily = getRollingAverage(stateItem);
|
||||||
stateItem.doublingDaily = getDoublingTime(stateItem);
|
stateItem.doublingDaily = getDoublingTime(stateItem);
|
||||||
stateItem.cases.rollingAverageDaily = getRollingAverage(stateItem.cases);
|
stateItem.cases.rollingAverageDaily = getRollingAverage(stateItem.cases);
|
||||||
|
stateItem.cases.doublingDaily = getDoublingTime(stateItem.cases);
|
||||||
|
|
||||||
// insert into states array for the country
|
// insert into states array for the country
|
||||||
perCountryTotals[item.country].states.push(stateItem);
|
perCountryTotals[item.country].states.push(stateItem);
|
||||||
@ -672,36 +609,12 @@ const processRecords = async () => {
|
|||||||
population: item.population,
|
population: item.population,
|
||||||
deathsPerMillion: item.population > 0 ? item.total / item.population * 1000000 : 0,
|
deathsPerMillion: item.population > 0 ? item.total / item.population * 1000000 : 0,
|
||||||
casesPerMillion: item.population > 0 ? item.cases.total / item.population * 1000000 : 0,
|
casesPerMillion: item.population > 0 ? item.cases.total / item.population * 1000000 : 0,
|
||||||
timeSeriesDaily: Object.keys(item.timeSeriesDaily).sort().map((date) => {
|
timeSeriesDaily: convertTsObjectToArray(item.timeSeriesDaily),
|
||||||
return {
|
timeSeriesMonthly: convertTsObjectToArray(item.timeSeriesMonthly),
|
||||||
key: date,
|
|
||||||
value: item.timeSeriesDaily[date].value,
|
|
||||||
delta: item.timeSeriesDaily[date].delta,
|
|
||||||
};
|
|
||||||
}),
|
|
||||||
timeSeriesMonthly: Object.keys(item.timeSeriesMonthly).sort().map((date) => {
|
|
||||||
return {
|
|
||||||
key: date,
|
|
||||||
value: item.timeSeriesMonthly[date].value,
|
|
||||||
delta: item.timeSeriesMonthly[date].delta,
|
|
||||||
};
|
|
||||||
}),
|
|
||||||
cases: {
|
cases: {
|
||||||
total: item.cases.total,
|
total: item.cases.total,
|
||||||
timeSeriesDaily: Object.keys(item.cases.timeSeriesDaily).sort().map((date) => {
|
timeSeriesDaily: convertTsObjectToArray(item.cases.timeSeriesDaily),
|
||||||
return {
|
timeSeriesMonthly: convertTsObjectToArray(item.cases.timeSeriesMonthly),
|
||||||
key: date,
|
|
||||||
value: item.cases.timeSeriesDaily[date].value,
|
|
||||||
delta: item.cases.timeSeriesDaily[date].delta,
|
|
||||||
};
|
|
||||||
}),
|
|
||||||
timeSeriesMonthly: Object.keys(item.cases.timeSeriesMonthly).sort().map((date) => {
|
|
||||||
return {
|
|
||||||
key: date,
|
|
||||||
value: item.cases.timeSeriesMonthly[date].value,
|
|
||||||
delta: item.cases.timeSeriesMonthly[date].delta,
|
|
||||||
};
|
|
||||||
}),
|
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -710,6 +623,7 @@ const processRecords = async () => {
|
|||||||
countryItem.rollingAverageDaily = getRollingAverage(countryItem);
|
countryItem.rollingAverageDaily = getRollingAverage(countryItem);
|
||||||
countryItem.doublingDaily = getDoublingTime(countryItem);
|
countryItem.doublingDaily = getDoublingTime(countryItem);
|
||||||
countryItem.cases.rollingAverageDaily = getRollingAverage(countryItem.cases);
|
countryItem.cases.rollingAverageDaily = getRollingAverage(countryItem.cases);
|
||||||
|
countryItem.cases.doublingDaily = getDoublingTime(countryItem.cases);
|
||||||
return countryItem;
|
return countryItem;
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -731,77 +645,23 @@ const processRecords = async () => {
|
|||||||
worldData.total += countryData.total;
|
worldData.total += countryData.total;
|
||||||
worldData.cases.total += countryData.cases.total;
|
worldData.cases.total += countryData.cases.total;
|
||||||
|
|
||||||
countryData.timeSeriesDaily.forEach((ts) => {
|
mergeTsArrayIntoObject(countryData.timeSeriesDaily, worldData.timeSeriesDaily);
|
||||||
worldData.timeSeriesDaily[ts.key] = worldData.timeSeriesDaily[ts.key] || {
|
mergeTsArrayIntoObject(countryData.timeSeriesMonthly, worldData.timeSeriesMonthly);
|
||||||
value: 0,
|
mergeTsArrayIntoObject(countryData.cases.timeSeriesDaily, worldData.cases.timeSeriesDaily);
|
||||||
delta: 0,
|
mergeTsArrayIntoObject(countryData.cases.timeSeriesMonthly, worldData.cases.timeSeriesMonthly);
|
||||||
};
|
|
||||||
worldData.timeSeriesDaily[ts.key].value += ts.value;
|
|
||||||
worldData.timeSeriesDaily[ts.key].delta += ts.delta;
|
|
||||||
});
|
|
||||||
|
|
||||||
countryData.timeSeriesMonthly.forEach((ts) => {
|
|
||||||
worldData.timeSeriesMonthly[ts.key] = worldData.timeSeriesMonthly[ts.key] || {
|
|
||||||
value: 0,
|
|
||||||
delta: 0,
|
|
||||||
};
|
|
||||||
worldData.timeSeriesMonthly[ts.key].value += ts.value;
|
|
||||||
worldData.timeSeriesMonthly[ts.key].delta += ts.delta;
|
|
||||||
});
|
|
||||||
|
|
||||||
countryData.cases.timeSeriesDaily.forEach((ts) => {
|
|
||||||
worldData.cases.timeSeriesDaily[ts.key] = worldData.cases.timeSeriesDaily[ts.key] || {
|
|
||||||
value: 0,
|
|
||||||
delta: 0,
|
|
||||||
};
|
|
||||||
worldData.cases.timeSeriesDaily[ts.key].value += ts.value;
|
|
||||||
worldData.cases.timeSeriesDaily[ts.key].delta += ts.delta;
|
|
||||||
});
|
|
||||||
|
|
||||||
countryData.cases.timeSeriesMonthly.forEach((ts) => {
|
|
||||||
worldData.cases.timeSeriesMonthly[ts.key] = worldData.cases.timeSeriesMonthly[ts.key] || {
|
|
||||||
value: 0,
|
|
||||||
delta: 0,
|
|
||||||
};
|
|
||||||
worldData.cases.timeSeriesMonthly[ts.key].value += ts.value;
|
|
||||||
worldData.cases.timeSeriesMonthly[ts.key].delta += ts.delta;
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
|
|
||||||
worldData.timeSeriesDaily = Object.keys(worldData.timeSeriesDaily).sort().map((date) => {
|
worldData.timeSeriesDaily = convertTsObjectToArray(worldData.timeSeriesDaily);
|
||||||
return {
|
worldData.timeSeriesMonthly = convertTsObjectToArray(worldData.timeSeriesMonthly);
|
||||||
key: date,
|
worldData.cases.timeSeriesDaily = convertTsObjectToArray(worldData.cases.timeSeriesDaily);
|
||||||
value: worldData.timeSeriesDaily[date].value,
|
worldData.cases.timeSeriesMonthly = convertTsObjectToArray(worldData.cases.timeSeriesMonthly);
|
||||||
delta: worldData.timeSeriesDaily[date].delta,
|
|
||||||
};
|
|
||||||
});
|
|
||||||
worldData.timeSeriesMonthly = Object.keys(worldData.timeSeriesMonthly).sort().map((date) => {
|
|
||||||
return {
|
|
||||||
key: date,
|
|
||||||
value: worldData.timeSeriesMonthly[date].value,
|
|
||||||
delta: worldData.timeSeriesMonthly[date].delta,
|
|
||||||
};
|
|
||||||
});
|
|
||||||
worldData.cases.timeSeriesDaily = Object.keys(worldData.cases.timeSeriesDaily).sort().map((date) => {
|
|
||||||
return {
|
|
||||||
key: date,
|
|
||||||
value: worldData.cases.timeSeriesDaily[date].value,
|
|
||||||
delta: worldData.cases.timeSeriesDaily[date].delta,
|
|
||||||
};
|
|
||||||
});
|
|
||||||
worldData.cases.timeSeriesMonthly = Object.keys(worldData.cases.timeSeriesMonthly).sort().map((date) => {
|
|
||||||
return {
|
|
||||||
key: date,
|
|
||||||
value: worldData.cases.timeSeriesMonthly[date].value,
|
|
||||||
delta: worldData.cases.timeSeriesMonthly[date].delta,
|
|
||||||
};
|
|
||||||
});
|
|
||||||
|
|
||||||
worldData.deathGrowthRate = getGrowthRate(worldData.timeSeriesDaily);
|
worldData.deathGrowthRate = getGrowthRate(worldData.timeSeriesDaily);
|
||||||
worldData.casesGrowthRate = getGrowthRate(worldData.cases.timeSeriesDaily);
|
worldData.casesGrowthRate = getGrowthRate(worldData.cases.timeSeriesDaily);
|
||||||
worldData.rollingAverageDaily = getRollingAverage(worldData);
|
worldData.rollingAverageDaily = getRollingAverage(worldData);
|
||||||
worldData.doublingDaily = getDoublingTime(worldData);
|
worldData.doublingDaily = getDoublingTime(worldData);
|
||||||
worldData.cases.rollingAverageDaily = getRollingAverage(worldData.cases);
|
worldData.cases.rollingAverageDaily = getRollingAverage(worldData.cases);
|
||||||
|
worldData.cases.doublingDaily = getDoublingTime(worldData.cases);
|
||||||
|
|
||||||
worldData.population = 7781841000;
|
worldData.population = 7781841000;
|
||||||
worldData.deathsPerMillion = worldData.total / worldData.population * 1000000;
|
worldData.deathsPerMillion = worldData.total / worldData.population * 1000000;
|
||||||
|
140
tmpl/master.pug
140
tmpl/master.pug
@ -25,6 +25,22 @@ html
|
|||||||
bottom: -1px;
|
bottom: -1px;
|
||||||
border-bottom: 2px solid #b5b5b5;
|
border-bottom: 2px solid #b5b5b5;
|
||||||
}
|
}
|
||||||
|
#hero-tooltip table {
|
||||||
|
border-collapse: collapse;
|
||||||
|
}
|
||||||
|
#hero-tooltip th, #hero-tooltip td {
|
||||||
|
padding: 2px 4px;
|
||||||
|
}
|
||||||
|
[class^="tooltip-color-"] {
|
||||||
|
width: 10px;
|
||||||
|
height: 10px;
|
||||||
|
border: 2px solid black;
|
||||||
|
display: inline-block;
|
||||||
|
}
|
||||||
|
[class^="tooltip-value-"] {
|
||||||
|
text-align: right;
|
||||||
|
font-family: monospace;
|
||||||
|
}
|
||||||
.geo-bg-dark {
|
.geo-bg-dark {
|
||||||
background-color: #8e8e8e;
|
background-color: #8e8e8e;
|
||||||
color: white;
|
color: white;
|
||||||
@ -223,6 +239,7 @@ html
|
|||||||
doubling,
|
doubling,
|
||||||
totalCases,
|
totalCases,
|
||||||
newCases,
|
newCases,
|
||||||
|
doublingCases,
|
||||||
) {
|
) {
|
||||||
const canvas = document.getElementById(id);
|
const canvas = document.getElementById(id);
|
||||||
charts.heroMaxValue = totalCases.reduce((max, value) => Math.max(max, value), 0);
|
charts.heroMaxValue = totalCases.reduce((max, value) => Math.max(max, value), 0);
|
||||||
@ -234,7 +251,8 @@ html
|
|||||||
const totalData = totalDeaths.slice(start, end);
|
const totalData = totalDeaths.slice(start, end);
|
||||||
const newData = newDeaths.slice(start, end);
|
const newData = newDeaths.slice(start, end);
|
||||||
const rollingData = rollingAverage.slice(start, end);
|
const rollingData = rollingAverage.slice(start, end);
|
||||||
const doublingData = doubling.slice(start, end);
|
const deathDoublingData = doubling.slice(start, end);
|
||||||
|
const caseDoublingData = doublingCases.slice(start, end);
|
||||||
const totalCaseData = totalCases.slice(start, end);
|
const totalCaseData = totalCases.slice(start, end);
|
||||||
const newCaseData = newCases.slice(start, end);
|
const newCaseData = newCases.slice(start, end);
|
||||||
|
|
||||||
@ -286,19 +304,30 @@ html
|
|||||||
label: 'New Deaths',
|
label: 'New Deaths',
|
||||||
data: newData,
|
data: newData,
|
||||||
fill: false,
|
fill: false,
|
||||||
borderColor: 'rgb(96, 96, 96, 0.25)',
|
borderColor: 'rgb(20,24,59, 0.15)',
|
||||||
backgroundColor: 'rgb(96, 96, 96, 0.25)',
|
backgroundColor: 'rgb(20,24,59, 0.15)',
|
||||||
borderWidth: 1,
|
borderWidth: 1,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: 'Days to 2x deaths',
|
label: 'Days to 2x deaths',
|
||||||
data: doublingData,
|
data: deathDoublingData,
|
||||||
fill: false,
|
fill: false,
|
||||||
borderColor: 'rgb(187,40,193, 0.5)',
|
borderColor: 'rgba(127,30,75,0.5)',
|
||||||
backgroundColor: 'rgb(187,40,193, 0.5)',
|
backgroundColor: 'rgb(127,30,75, 0.5)',
|
||||||
borderWidth: 2,
|
borderWidth: 2,
|
||||||
pointRadius: 0,
|
pointRadius: 0,
|
||||||
}
|
borderDash: [ 4, 4 ],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'Days to 2x cases',
|
||||||
|
data: caseDoublingData,
|
||||||
|
fill: false,
|
||||||
|
borderColor: 'rgba(87,86,38,0.5)',
|
||||||
|
backgroundColor: 'rgb(87,86,38, 0.5)',
|
||||||
|
borderWidth: 2,
|
||||||
|
pointRadius: 0,
|
||||||
|
borderDash: [ 4, 4 ],
|
||||||
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
options: {
|
options: {
|
||||||
@ -313,6 +342,102 @@ html
|
|||||||
intersect: false,
|
intersect: false,
|
||||||
position: 'middle',
|
position: 'middle',
|
||||||
axis: 'x',
|
axis: 'x',
|
||||||
|
enabled: false,
|
||||||
|
custom: function(tooltipModel) {
|
||||||
|
let tooltipEl = document.getElementById('hero-tooltip');
|
||||||
|
|
||||||
|
// Create element on first render
|
||||||
|
if (!tooltipEl) {
|
||||||
|
tooltipEl = document.createElement('div');
|
||||||
|
tooltipEl.id = 'hero-tooltip';
|
||||||
|
const border = '1px solid #606060';
|
||||||
|
tooltipEl.innerHTML = `
|
||||||
|
<div class="text-center" style="font-size: 125%; border-bottom: ${border};">
|
||||||
|
<strong class="tooltip-title"></strong>
|
||||||
|
</div>
|
||||||
|
<table>
|
||||||
|
<tr>
|
||||||
|
<td><span class="tooltip-color-cases-total"></span></td>
|
||||||
|
<th>Total Cases</th>
|
||||||
|
<td class="tooltip-value-cases-total"></td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td><span class="tooltip-color-cases-new"></span></td>
|
||||||
|
<th>New Cases</th>
|
||||||
|
<td class="tooltip-value-cases-new"></td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td><span class="tooltip-color-deaths-total"></span></td>
|
||||||
|
<th>Total Deaths</th>
|
||||||
|
<td class="tooltip-value-deaths-total"></td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td><span class="tooltip-color-deaths-new"></span></td>
|
||||||
|
<th>New Deaths</th>
|
||||||
|
<td class="tooltip-value-deaths-new"></td>
|
||||||
|
</tr>
|
||||||
|
</table>
|
||||||
|
<div class="text-center pt-1" style="border-top: ${border}">
|
||||||
|
Deaths 2x every <strong class="tooltip-2x-deaths"></strong><br />
|
||||||
|
Cases 2x every <strong class="tooltip-2x-cases"></strong><br />
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
tooltipEl.style.zIndex = '1000';
|
||||||
|
document.body.appendChild(tooltipEl);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (tooltipModel.opacity === 0) {
|
||||||
|
tooltipEl.style.opacity = '0';
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
tooltipEl.style.backgroundImage = 'linear-gradient(to bottom, rgba(52, 52, 52, 0.75), rgba(24, 24, 24, 0.75))';
|
||||||
|
tooltipEl.style.color = 'white';
|
||||||
|
tooltipEl.style.textShadow = '1px 1px 1px black';
|
||||||
|
tooltipEl.style.borderRadius = '2px';
|
||||||
|
tooltipEl.style.boxShadow = '2px 2px 3px rgba(0, 0, 0, 0.75)';
|
||||||
|
|
||||||
|
if (tooltipModel.dataPoints) {
|
||||||
|
tooltipEl.querySelector('.tooltip-title').textContent = tooltipModel.title.join(' ');
|
||||||
|
const setData = (cls, index, colorIndex) => {
|
||||||
|
colorIndex = typeof(colorIndex) === 'number' ? colorIndex : index;
|
||||||
|
const color = tooltipModel.labelColors[colorIndex];
|
||||||
|
const value = Number(tooltipModel.dataPoints[index].value).toLocaleString();
|
||||||
|
tooltipEl.querySelector('.tooltip-value-' + cls).textContent = value;
|
||||||
|
const colorEl = tooltipEl.querySelector('.tooltip-color-' + cls);
|
||||||
|
colorEl.style.backgroundColor = color.backgroundColor;
|
||||||
|
colorEl.style.borderColor = color.borderColor;
|
||||||
|
};
|
||||||
|
|
||||||
|
setData('cases-total', 0);
|
||||||
|
setData('cases-new', 1);
|
||||||
|
setData('deaths-total', 2);
|
||||||
|
setData('deaths-new', 4, 3);
|
||||||
|
|
||||||
|
const daysToDoubleDeaths = Number(tooltipModel.dataPoints[5].value).toLocaleString();
|
||||||
|
const daysToDoubleCases = Number(tooltipModel.dataPoints[6].value).toLocaleString();
|
||||||
|
tooltipEl.querySelector('.tooltip-2x-deaths').textContent = daysToDoubleDeaths + ' days';
|
||||||
|
tooltipEl.querySelector('.tooltip-2x-cases').textContent = daysToDoubleCases + ' days';
|
||||||
|
}
|
||||||
|
|
||||||
|
const position = this._chart.canvas.getBoundingClientRect();
|
||||||
|
|
||||||
|
// Display, position, and set styles for font
|
||||||
|
tooltipEl.style.opacity = '1';
|
||||||
|
tooltipEl.style.position = 'absolute';
|
||||||
|
|
||||||
|
tooltipEl.style.fontFamily = tooltipModel._bodyFontFamily;
|
||||||
|
tooltipEl.style.fontSize = tooltipModel.bodyFontSize + 'px';
|
||||||
|
tooltipEl.style.fontStyle = tooltipModel._bodyFontStyle;
|
||||||
|
tooltipEl.style.padding = tooltipModel.yPadding + 'px ' + tooltipModel.xPadding + 'px';
|
||||||
|
tooltipEl.style.pointerEvents = 'none';
|
||||||
|
|
||||||
|
const tooltipSize = tooltipEl.getBoundingClientRect();
|
||||||
|
const chartArea = this._chart.chartArea;
|
||||||
|
const chartHeight = chartArea.bottom - chartArea.top;
|
||||||
|
tooltipEl.style.left = (position.left + window.pageXOffset + tooltipModel.x) + 'px';
|
||||||
|
tooltipEl.style.top = (position.top + window.pageYOffset + chartArea.top + (chartHeight / 2) - (tooltipSize.height / 2)) + 'px';
|
||||||
|
}
|
||||||
},
|
},
|
||||||
scales: {
|
scales: {
|
||||||
yAxes: [
|
yAxes: [
|
||||||
@ -400,6 +525,7 @@ html
|
|||||||
!{JSON.stringify(data.doublingDaily.map(x => x.value))},
|
!{JSON.stringify(data.doublingDaily.map(x => x.value))},
|
||||||
!{JSON.stringify(data.cases.timeSeriesDaily.map(x => x.value))},
|
!{JSON.stringify(data.cases.timeSeriesDaily.map(x => x.value))},
|
||||||
!{JSON.stringify(data.cases.timeSeriesDaily.map(x => x.delta))},
|
!{JSON.stringify(data.cases.timeSeriesDaily.map(x => x.delta))},
|
||||||
|
!{JSON.stringify(data.cases.doublingDaily.map(x => x.value))},
|
||||||
);
|
);
|
||||||
|
|
||||||
mixin dataTable(items, label, type)
|
mixin dataTable(items, label, type)
|
||||||
|
Loading…
Reference in New Issue
Block a user