Add deaths chart & new data.

This commit is contained in:
Joshua Bemenderfer
2021-12-31 15:30:43 -05:00
parent c19601b8f3
commit 6da7af9c3e
665 changed files with 1228 additions and 497 deletions

View File

@@ -92,7 +92,7 @@ const chips = reactive({
? ((change / yesterday) * 100)
: ((change / today) * 100)
if (percent === Infinity) return 100
if (Math.abs(percent) === Infinity) return 100
if (isNaN(percent)) return 0
return Math.abs(percent.toFixed(1))
@@ -102,7 +102,7 @@ const chips = reactive({
if (!data.value) return null
const r = data.value.rows.at(-1)
return Math.round(col(data, r, 'cases_per_capita') * 10000)
return parseFloat((col(data, r, 'cases_per_capita') * 10000).toFixed(1))
}),
total_cases: computed(() => {
@@ -131,7 +131,7 @@ const chips = reactive({
? ((change / yesterday) * 100)
: ((change / today) * 100)
if (percent === Infinity) return 100
if (Math.abs(percent) === Infinity) return 100
if (isNaN(percent)) return 0
return Math.abs(percent.toFixed(1))
@@ -141,7 +141,7 @@ const chips = reactive({
if (!data.value) return null
const r = data.value.rows.at(-1)
return Math.round(col(data, r, 'total_cases_per_capita') * 10000)
return parseFloat((col(data, r, 'total_cases_per_capita') * 10000).toFixed(1))
})
})

View File

@@ -0,0 +1,148 @@
<template>
<StatCard>
<template v-slot:heading>Confirmed Deaths {{last_report_date ? humanDate(last_report_date) : ''}}</template>
<template v-slot:value v-if="chips.current_deaths != null">
{{chips.current_deaths.toLocaleString()}}
</template>
<template v-slot:meta v-if="chips.current_death_change != null">
<span v-if="chips.current_death_change > 0">Up </span>
<span v-if="chips.current_death_change < 0">Down </span>
<span :class="{'font-semibold': true, 'text-red-600': chips.current_death_change > 0, 'text-green-600': chips.current_death_change < 0}">
{{Math.abs(chips.current_death_change).toLocaleString()}} ({{chips.current_death_change_percent}}%)
</span> compared to previous day
</template>
</StatCard>
<StatCard>
<template v-slot:heading>Deaths per 100,000 Residents {{last_report_date ? humanDate(last_report_date) : ''}}</template>
<template v-slot:value v-if="chips.current_deaths_per_capita != null">
~{{chips.current_deaths_per_capita.toLocaleString()}}
</template>
<template v-slot:meta v-if="chips.current_deaths != null && chips.population != null">
<span class="text-indigo-500 font-semibold">{{chips.current_deaths.toLocaleString()}}</span> out of <span class="text-indigo-500 font-semibold">{{chips.population.toLocaleString()}}</span> residents
</template>
</StatCard>
<StatCard>
<template v-slot:heading>Total Confirmed Deaths</template>
<template v-slot:value v-if="chips.total_deaths != null">
{{chips.total_deaths.toLocaleString()}}
</template>
<template v-slot:meta v-if="chips.total_death_change != null">
<span class="font-semibold text-indigo-500">
{{Math.abs(chips.total_death_change).toLocaleString()}} ({{chips.total_death_change_percent}}%)
</span> more than previous day
</template>
</StatCard>
<StatCard>
<template v-slot:heading>Total Deaths per 100,000 Residents</template>
<template v-slot:value v-if="chips.total_deaths_per_capita != null">
~{{chips.total_deaths_per_capita.toLocaleString()}}
</template>
<template v-slot:meta v-if="chips.total_deaths != null && chips.population != null">
<span class="text-indigo-500 font-semibold">{{chips.total_deaths.toLocaleString()}}</span> out of <span class="text-indigo-500 font-semibold">{{chips.population.toLocaleString()}}</span> residents
</template>
</StatCard>
</template>
<script setup>
import { reactive, computed } from 'vue'
import { col, humanDate } from '@/components/charts/util'
import store from '@/components/charts/overall/deaths/store.js'
const data = store.data
const last_report_date = computed(() => {
if (!data.value) return null
return col(data, data.value.rows.at(-1), 'report_date')
})
const chips = reactive({
population: computed(() => {
if (!data.value) return null
const r = data.value.rows.at(-1)
return col(data, r, 'population')
}),
current_deaths: computed(() => {
if (!data.value) return null
const r = data.value.rows.at(-1)
return col(data, r, 'deaths')
}),
current_death_change: computed(() => {
if (!data.value) return null
const current = col(data, data.value.rows.at(-1), 'deaths')
const prev = col(data, data.value.rows.at(-2), 'deaths')
return current - prev
}),
current_death_change_percent: computed(() => {
if (!data.value) return null
const current = col(data, data.value.rows.at(-1), 'deaths')
const prev = col(data, data.value.rows.at(-2), 'deaths')
const change = current - prev
const percent = change > 0
? ((change / prev) * 100)
: ((change / current) * 100)
if (Math.abs(percent) === Infinity) return 100
if (isNaN(percent)) return 0
return Math.abs(percent.toFixed(1))
}),
current_deaths_per_capita: computed(() => {
if (!data.value) return null
const r = data.value.rows.at(-1)
return parseFloat((col(data, r, 'deaths_per_capita') * 100000).toFixed(1))
}),
total_deaths: computed(() => {
if (!data.value) return null
const r = data.value.rows.at(-1)
return col(data, r, 'total_deaths')
}),
total_death_change: computed(() => {
if (!data.value) return null
const current = col(data, data.value.rows.at(-1), 'total_deaths')
const prev = current - col(data, data.value.rows.at(-1), 'deaths')
return current - prev
}),
total_death_change_percent: computed(() => {
if (!data.value) return null
const current = col(data, data.value.rows.at(-1), 'total_deaths')
const prev = current - col(data, data.value.rows.at(-1), 'deaths')
const change = current - prev
const percent = change > 0
? ((change / prev) * 100)
: ((change / current) * 100)
if (Math.abs(percent) === Infinity) return 100
if (isNaN(percent)) return 0
return Math.abs(percent.toFixed(1))
}),
total_deaths_per_capita: computed(() => {
if (!data.value) return null
const r = data.value.rows.at(-1)
return parseFloat((col(data, r, 'total_deaths_per_capita') * 100000).toFixed(1))
})
})
</script>

View File

@@ -0,0 +1,67 @@
<template>
<Card class="flex flex-col">
<h2 class="mb-1 text-xl text-indigo-900 flex justify-between items-center">
Total Deaths per 10,000 Residents
</h2>
<p class="mb-4 text-sm text-indigo-700">Since Feburary 1, 2020</p>
<JSCharting :options="mapOptions" class="flex-1"/>
</Card>
</template>
<script setup>
import { computed, onMounted } from 'vue'
import { col, colors, formatDate} from '@/components/charts/util'
import store from '@/components/charts/overall/deaths/store_combined.js'
const column = 'total_deaths'
const data = store.data
const rows = computed(() => {
if (!data.value) return []
return data.value.rows
})
const mapOptions = computed(() => {
if (!data.value) return {}
const current_date = data.value.segment.report_date.at(-1)
return {
type: 'map solid',
legend_position: 'bottom',
palette: {
pointValue: point => {
return point?.userOptions?.attributes?.rate || 0
},
colors
},
defaultPoint: {
tooltip: '%name County <b>(%rate)</b>',
states: {
hover: { fill: 'currentColor' }
}
},
series: [{
map: '/maps/ga-13-georgia-counties.json',
points: rows.value
.filter(r => {
return col(data, r, 'report_date') === current_date
})
.map(r => {
const id = `ga-13-georgia-counties.${col(data, r, 'county').toLowerCase().split(' ').join('-')}`
return {
map: id,
attributes: {
rate: Math.round(col(data, r, 'total_deaths_per_capita') * 10000)
}
}
})
}],
}
})
onMounted(() => {
var chart;
var countiesData;
})
</script>

View File

@@ -0,0 +1,9 @@
<template>
<SliceSelector :parameters="parameters" :unknown="true"></SliceSelector>
</template>
<script setup lang='ts'>
import store from '@/components/charts/overall/deaths/store.js'
const parameters = store.parameters
</script>

View File

@@ -0,0 +1,68 @@
<template>
<Card>
<h2 class="mb-6 text-xl text-indigo-900 flex justify-between items-center">
Daily Deaths
<select v-model="chart" class="text-base m-0">
<option value="calendar">Calendar</option>
<option value="line">Line Chart</option>
</select>
</h2>
<JSCharting v-if="chart === 'calendar'" :options="calendarOptions"></JSCharting>
<JSCharting v-if="chart === 'line'" :options="areaOptions"></JSCharting>
</Card>
</template>
<script setup>
import { ref, computed } from 'vue'
import { col, colors } from '@/components/charts/util'
import store from '@/components/charts/overall/deaths/store.js'
const chart = ref('calendar')
const column = 'deaths'
const parameters = store.parameters
const data = store.data
const rows = computed(() => {
if (!data.value || !parameters.value) return []
return data.value.rows
.filter(r => {
return col(data, r, 'report_date') >= parameters.value.start && col(data, r, 'report_date') <= parameters.value.end
})
})
const calendarOptions = computed(() => {
return {
type: 'calendar month solid',
palette: { colors },
calendar_initial: parameters.value ? parameters.value.end : null,
data: rows.value.map(r => ([
`${col(data, r, 'report_date')} 23:59:59`,
col(data, r, column)
]))
}
})
const areaOptions = computed(() => {
return {
type: 'lineSpline',
legend_visible: false,
xAxis: { crosshair_enabled: true, scale_type: 'time' },
palette: { colors },
defaultSeries: {
shape_opacity: 0.55,
color: colors[Math.round(colors.length / 2)],
defaultPoint_marker_visible: false
},
series: [
{
points: rows.value.map(r => ([
`${col(data, r, 'report_date')} 23:59:59`,
col(data, r, column)
]))
}
]
}
})
</script>

View File

@@ -0,0 +1,48 @@
<template>
<Card>
<h2 class="mb-6 text-xl text-indigo-900 flex justify-between items-center">
Total Deaths
</h2>
<JSCharting :options="areaOptions"></JSCharting>
</Card>
</template>
<script setup>
import { ref, computed } from 'vue'
import { col, colors } from '@/components/charts/util'
import store from '@/components/charts/overall/deaths/store.js'
const column = 'total_deaths'
const parameters = store.parameters
const data = store.data
const rows = computed(() => {
if (!data.value || !parameters.value) return []
return data.value.rows
.filter(r => {
return col(data, r, 'report_date') >= parameters.value.start && col(data, r, 'report_date') <= parameters.value.end
})
})
const areaOptions = computed(() => {
return {
type: 'lineSpline',
legend_visible: false,
xAxis: { crosshair_enabled: true, scale_type: 'time' },
palette: { colors },
defaultSeries: {
shape_opacity: 0.55,
color: colors[Math.round(colors.length / 2)],
defaultPoint_marker_visible: false
},
series: [
{
points: rows.value.map(r => ([
`${col(data, r, 'report_date')} 23:59:59`,
col(data, r, column)
]))
}
]
}
})
</script>

View File

@@ -0,0 +1,35 @@
import { reactive, ref, watch } from 'vue'
import { col, formatDate } from '@/components/charts/util.js'
const today = new Date()
const end = new Date()
const start = new Date(today.valueOf() - 2592000000)
const store = {
parameters: ref({
county: '-- All --',
start: formatDate(start),
end: formatDate(end)
}),
data: ref(null)
}
async function refreshData() {
store.data.value = await fetch(`/data/state/deaths/by-county/${store.parameters.value.county}.json`).then(res => res.json())
// Annoyingly complicated way to reset the end date to the most recent report date.
if (formatDate(end) === formatDate(new Date())) {
const mostRecentReportDate = formatDate(new Date(col(store.data, store.data.value.rows.at(-1), 'report_date') + ' 23:59:59'))
store.parameters.value.end = mostRecentReportDate
}
}
watch(() => store.parameters.value.county, () => {
refreshData()
})
if (globalThis.window) {
refreshData()
}
export default store

View File

@@ -0,0 +1,15 @@
import { reactive, ref, watch } from 'vue'
const store = {
data: ref(null)
}
async function refreshData() {
store.data.value = await fetch(`/data/state/deaths/combined.json`).then(res => res.json())
}
if (globalThis.window) {
refreshData()
}
export default store

View File

@@ -70,7 +70,7 @@ const chips = reactive({
? ((change / yesterday) * 100)
: ((change / today) * 100)
if (percent === Infinity) return 100
if (Math.abs(percent) === Infinity) return 100
if (isNaN(percent)) return 0
return Math.abs(percent.toFixed(1))
@@ -87,14 +87,14 @@ const chips = reactive({
if (!data.value) return null
const r = data.value.rows.at(-1)
return Math.round(col(data, r, 'hospitalizations_last_14_days_per_capita') * 10000)
return parseFloat((col(data, r, 'hospitalizations_last_14_days_per_capita') * 10000).toFixed(1))
}),
total_hospitalizations_per_capita: computed(() => {
if (!data.value) return null
const r = data.value.rows.at(-1)
return Math.round(col(data, r, 'total_hospitalizations_per_capita') * 10000)
return parseFloat((col(data, r, 'total_hospitalizations_per_capita') * 10000).toFixed(1))
})
})

View File

@@ -23,11 +23,11 @@
<icon-mdi-hospital-box-outline class="mr-2"></icon-mdi-hospital-box-outline> hospitalizations?
</router-link>
</li>
<!--<li>
<router-link :to="{path: '/cases'}" class="text-base rounded-lg flex items-center p-2 mr-3 hover:bg-violet-100 group" active-class="bg-violet-800 text-white hover:bg-violet-800">
<li>
<router-link :to="{path: '/overall/deaths'}" class="text-base rounded-lg flex items-center p-2 mr-3 hover:bg-violet-100 group" active-class="bg-violet-800 text-white hover:bg-violet-800">
<icon-mdi-grave-stone class="mr-2"></icon-mdi-grave-stone> deaths?
</router-link>
</li> -->
</li>
</ul>
</li>
<!-- <li>

View File

@@ -0,0 +1,35 @@
---
title: What is the overall trend in deaths?
---
<div class="grid gap-2 lg:gap-4 grid-cols-1 xl:grid-cols-2">
<Card class="col-span-1" prose={true}>
# What is the overall trend in deaths?
## What is this report useful for?
This report allows you to see which counties and areas have been most affected by the pandemic.
Death counts can provide indication of how damaging a given wave of the pandemic roughly 2-4 weeks after the worst has passed.
Deaths are a **lagging indicator**, as it may take weeks or even months before an infected patient passes away.
Accordingly, deaths counts are ineffective for personal decisionmaking as they only give you insight into the past.
*If death counts **do** closely follow case counts, that may indicate an outbreak of a highly-dangerous variant.*<br/>
*If 4 weeks to a month after an increase in cases deaths do not appreciably rise, that may indicate that a particular variant is relatively benign.*
</Card>
<MapDeaths client:load/>
</div>
<ParametersDeaths client:load/>
<div class="grid gap-2 lg:gap-4 grid-cols-1 sm:grid-cols-2 xl:grid-cols-4">
<ChipsDeaths client:load/>
</div>
<div class="grid gap-2 lg:gap-4 grid-cols-1 xl:grid-cols-2">
<TrendDailyDeaths client:load/>
<TrendTotalDeaths client:load/>
</div>