Add basic risk by age dashboard.
This commit is contained in:
@@ -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 = [
|
||||
|
||||
@@ -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())) {
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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())) {
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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())) {
|
||||
|
||||
@@ -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, () => {
|
||||
|
||||
9
src/components/charts/risk/age/ParametersRiskAge.vue
Normal file
9
src/components/charts/risk/age/ParametersRiskAge.vue
Normal 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>
|
||||
81
src/components/charts/risk/age/TrendRiskAge.vue
Normal file
81
src/components/charts/risk/age/TrendRiskAge.vue
Normal 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>
|
||||
30
src/components/charts/risk/age/store.js
Normal file
30
src/components/charts/risk/age/store.js
Normal 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
|
||||
@@ -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>
|
||||
|
||||
@@ -2,4 +2,7 @@
|
||||
title: Introduction
|
||||
---
|
||||
|
||||
# Introduction
|
||||
<Card prose={true}>
|
||||
# Introduction
|
||||
|
||||
</Card>
|
||||
|
||||
18
src/pages/risk/age.mdx
Normal file
18
src/pages/risk/age.mdx
Normal 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>
|
||||
Reference in New Issue
Block a user