Add basic risk by age dashboard.

This commit is contained in:
Joshua Bemenderfer
2021-12-31 17:27:35 -05:00
parent 000a28ebdc
commit be2a75a579
671 changed files with 227 additions and 22 deletions

View File

@@ -1,20 +1,20 @@
<template>
<div v-if="parameters" class='flex flex-wrap bg-white shadow rounded-lg p-4 space-x-6 mt-2 lg:px-6 lg:py-0 lg:fixed lg:z-50 lg:top-0 lg:right-0 lg:left-96 lg:h-16 lg:shadow-none lg:flex-nowrap lg:!mt-0'>
<label class='w-full flex items-center lg:inline-flex lg:w-auto'>
<label class='w-full flex items-center lg:inline-flex lg:w-auto' v-if="showCounty">
County
<select v-model="parameters.county" class='ml-4 flex-1'>
<select :value="parameters.county" @change="parameters.county = $event.target.value" class='ml-4 flex-1'>
<option v-for='county of counties' :key='county' :value='county'>{{county}}</option>
</select>
</label>
<label class='w-full flex items-center lg:inline-flex lg:w-auto'>
Start
<input type='date' v-model='parameters.start' class='ml-4 flex-1'/>
<input type='date' :value='parameters.start' @change="parameters.start = $event.target.value" class='ml-4 flex-1'/>
</label>
<label class='w-full flex items-center lg:inline-flex lg:w-auto'>
End
<input type='date' v-model='parameters.end' class='ml-4 flex-1'/>
<input type='date' :value='parameters.end' @change="parameters.end = $event.target.value" class='ml-4 flex-1'/>
</label>
</div>
</template>
@@ -22,7 +22,11 @@
<script setup lang='ts'>
const { parameters, unknown } = defineProps({
parameters: Object,
unknown: Boolean
unknown: Boolean,
showCounty: {
type: Boolean,
default: true
}
})
const counties = [

View File

@@ -15,7 +15,7 @@ const store = {
}
async function refreshData() {
store.data.value = await fetch(`/data/state/cases/by-county/${store.parameters.value.county}.json`).then(res => res.json())
store.data.value = await fetch(`/data/overall/cases/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())) {

View File

@@ -5,7 +5,7 @@ const store = {
}
async function refreshData() {
store.data.value = await fetch(`/data/state/cases/combined.json`).then(res => res.json())
store.data.value = await fetch(`/data/overall/cases/combined.json`).then(res => res.json())
}
if (globalThis.window) {

View File

@@ -15,7 +15,7 @@ const store = {
}
async function refreshData() {
store.data.value = await fetch(`/data/state/deaths/by-county/${store.parameters.value.county}.json`).then(res => res.json())
store.data.value = await fetch(`/data/overall/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())) {

View File

@@ -5,7 +5,7 @@ const store = {
}
async function refreshData() {
store.data.value = await fetch(`/data/state/deaths/combined.json`).then(res => res.json())
store.data.value = await fetch(`/data/overall/deaths/combined.json`).then(res => res.json())
}
if (globalThis.window) {

View File

@@ -15,7 +15,7 @@ const store = {
}
async function refreshData() {
store.data.value = await fetch(`/data/state/hospitalizations/by-county/${store.parameters.value.county}.json`).then(res => res.json())
store.data.value = await fetch(`/data/overall/hospitalizations/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())) {

View File

@@ -15,7 +15,7 @@ const store = {
}
async function refreshData() {
store.data.value = await fetch(`/data/state/testing/by-county/${store.parameters.value.county}.json`).then(res => res.json())
store.data.value = await fetch(`/data/overall/testing/by-county/${store.parameters.value.county}.json`).then(res => res.json())
}
watch(() => store.parameters.value.county, () => {

View File

@@ -0,0 +1,9 @@
<template>
<SliceSelector :parameters="parameters" :showCounty="false"></SliceSelector>
</template>
<script setup lang='ts'>
import store from '@/components/charts/risk/age/store.js'
const parameters = store.parameters
</script>

View File

@@ -0,0 +1,81 @@
<template>
<Card v-for="chart of charts" :key="chart.age_group">
<h2 class="mb-2 text-xl text-indigo-900 flex justify-between items-center">
Cases, hospitalizations, and deaths for persons {{formatAgeGroup(chart.age_group)}}
</h2>
<p class="mb-1 text-sm">From {{humanDate(parameters.start)}} to {{humanDate(parameters.end)}}
</p>
<p class="mb-4 text-sm text-indigo-700">Logarithmic scale used for clarity</p>
<JSCharting :options="chart.options"></JSCharting>
</Card>
</template>
<script setup>
import { ref, computed } from 'vue'
import { col, humanDate } from '@/components/charts/util'
import store from '@/components/charts/risk/age/store.js'
const column = 'cases'
const parameters = store.parameters
const data = store.data
const formatAgeGroup = (age_group) => {
if (age_group === 'Unknown years') return 'of unknown age'
if (age_group === '80 & Older') return '80+ years of age'
return `${age_group} of age`
}
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 charts = computed(() => {
if (!rows.value) return {}
const dataSet = rows.value.reduce((obj, r) => {
const age_group = col(data, r, 'age_group')
const report_date = col(data, r, 'report_date')
const cases = col(data, r, 'cases')
const hospitalizations = col(data, r, 'hospitalizations')
const deaths = col(data, r, 'deaths')
if (!obj[age_group]) obj[age_group] = {
age_group,
series: [
{ name: 'Cases', points: [] },
{ name: 'Hospitalizations', points: [] },
{ name: 'Deaths', points: [] }
]
}
obj[age_group].series[0].points.push([report_date, cases])
obj[age_group].series[1].points.push([report_date, hospitalizations])
obj[age_group].series[2].points.push([report_date, deaths])
return obj
}, {})
return Object.values(dataSet).map(({ age_group, series }) => {
return {
age_group,
options: {
type: 'lineSpline',
legend_visible: true,
debug: true,
xAxis: { crosshair_enabled: true, scale_type: 'time' },
yAxis: { scale: { type: 'logarithmic', logBase: 10 } },
legend_position: 'bottom',
defaultSeries: {
defaultPoint_marker_visible: false
},
series
}
}
})
})
</script>

View File

@@ -0,0 +1,30 @@
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({
start: formatDate(start),
end: formatDate(end)
}),
data: ref(null)
}
async function refreshData() {
store.data.value = await fetch(`/data/risk/age/age.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
}
}
if (globalThis.window) {
refreshData()
}
export default store

View File

@@ -55,30 +55,30 @@
</li>
</ul>
</li> -->
<!-- <li>
<li>
<span class="flex font-bold px-3 py-1 text-violet-900">Who is most at-risk...</span>
<ul class="space-y-1 py-2 ml-3">
<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">
<router-link :to="{path: '/risk/age'}" 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-human-cane class="mr-2"></icon-mdi-human-cane> by age?
</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">
<router-link :to="{path: '/risk/gender'}" 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-gender-male-female class="mr-2"></icon-mdi-gender-male-female> by gender?
</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">
<router-link :to="{path: '/risk/ethnicity'}" 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-web class="mr-2"></icon-mdi-web> by ethnicity?
</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">
<router-link :to="{path: '/risk/health-condition'}" 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-healthicons-clinical-f-outline class="mr-2"></icon-healthicons-clinical-f-outline> by health condition?
</router-link>
</li>
</ul>
</li> -->
</li>
</ul>
</template>

View File

@@ -2,4 +2,7 @@
title: Introduction
---
# Introduction
<Card prose={true}>
# Introduction
</Card>

18
src/pages/risk/age.mdx Normal file
View File

@@ -0,0 +1,18 @@
---
title: Who is most at risk by age?
---
<Card class="col-span-1" prose={true}>
# Who is most at risk by age?
## What is this report useful for?
This report provides charts indicating the number of cases, hospitalizations, and deaths in each age group.<br/>
Relative risk can be inferred by comparing the difference between case, hospitalization, and death rates in each group.
</Card>
<ParametersRiskAge client:load/>
<div class="grid gap-2 lg:gap-4 grid-cols-1 xl:grid-cols-2">
<TrendRiskAge client:load/>
</div>