Rework data loading, get first dashboard roughly in order.

This commit is contained in:
Joshua Bemenderfer
2021-12-30 13:25:31 -05:00
parent b8cca082ed
commit 38976cf29d
180 changed files with 632 additions and 263 deletions

View File

@@ -1,5 +1,11 @@
<template>
<div class="bg-white shadow rounded-lg p-4 lg:p-8 max-w-none prose lg:prose-base prose-headings:font-light prose-headings:text-indigo-900 prose-h1:mb-0">
<div :class="['bg-white shadow rounded-lg p-4 lg:p-8 max-w-none', prose ? 'prose lg:prose-base prose-headings:font-light prose-headings:text-indigo-900 prose-h1:mb-0' : '']">
<slot></slot>
</div>
</template>
<script setup>
defineProps({
prose: Boolean
})
</script>

View File

@@ -1,21 +0,0 @@
<template></template>
<script setup>
import { onMounted, watch } from 'vue'
const { parameters, data, watcher, refresher } = defineProps({
parameters: Object,
data: Object,
watcher: Function,
refresher: Function
})
console.log(parameters, data, watcher, refresher)
async function refreshData(parameters) {
data.value = await refresher(parameters)
}
watch(watcher, () => refreshData(parameters))
onMounted(() => refreshData(parameters))
</script>

View File

@@ -1,5 +1,5 @@
<template>
<div class='flex flex-wrap bg-white shadow rounded-lg p-4 space-x-6 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'>
<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'>
County
<select v-model="parameters.county" class='ml-4 flex-1'>
@@ -20,9 +20,9 @@
</template>
<script setup lang='ts'>
import { ref, inject } from 'vue'
import cache from '@/data/cache.js'
const parameters = inject('parameters')
const parameters = cache.parameters
const counties = [
'All',

View File

@@ -0,0 +1,7 @@
<template>
<Card>
<h2 class="text-xl font-light text-indigo-900 mb-2"><slot name="heading"></slot></h2>
<p class="text-3xl text-indigo-600 font-bold mb-3"><slot name="value"></slot></p>
<p class="text-base font-light"><slot name="meta"></slot></p>
</Card>
</template>

View File

@@ -1,28 +0,0 @@
<template>
<JSCharting v-if="data" :options="chartOptions"></JSCharting>
</template>
<script setup>
import { computed, inject } from 'vue'
import { colors, col } from './util.js'
const parameters = inject('parameters')
const data = inject('data')
const chartOptions = computed(() => {
if (!data.value) return
return {
type: 'calendar solid',
palette: { colors },
data: data.value.rows
.filter(r => {
return col(data, r, 'report_date') >= parameters.start && col(data, r, 'report_date') <= parameters.end
})
.map(r => ([
`${col(data, r, 'report_date')} 12:00:00`,
col(data, r, parameters.column)
]))
}
})
</script>

View File

@@ -0,0 +1,109 @@
<template>
<StatCard>
<template v-slot:heading>Total Tests Performed</template>
<template v-slot:value v-if="chips.total_tests">
{{chips.total_tests.toLocaleString()}}
</template>
<template v-slot:meta v-if="chips.total_tests">
PCR + Antigen
</template>
</StatCard>
<StatCard>
<template v-slot:heading>Highest Testing Day</template>
<template v-slot:value v-if="chips.tests">
{{chips.tests.date.toLocaleDateString('en-US', { year: 'numeric', month: 'long', day: 'numeric' })}}
</template>
<template v-slot:meta v-if="chips.tests">
<span class="text-indigo-500 font-bold">{{chips.tests.value.toLocaleString()}}</span> tests performed
</template>
</StatCard>
<StatCard>
<template v-slot:heading>Highest Positive Day</template>
<template v-slot:value v-if="chips.positive">
<span class="text-red-800">{{chips.positive.date.toLocaleDateString('en-US', { year: 'numeric', month: 'long', day: 'numeric' })}}</span>
</template>
<template v-slot:meta v-if="chips.positive">
<span class="text-red-800 font-bold">{{chips.positive.value.toLocaleString()}}</span> new cases
</template>
</StatCard>
<StatCard>
<template v-slot:heading>7-Day Percent Positive</template>
<template v-slot:value v-if="chips.percent_positive">
<span :class="{
'text-green-600': chips.percent_positive < 2,
'text-yellow-500': chips.percent_positive >= 2 && chips.percent_positive < 5,
'text-orange-500': chips.percent_positive >= 5 && chips.percent_positive < 20,
'text-red-800': chips.percent_positive > 20
}">
{{chips.percent_positive}}<span class="font-light">%</span>
</span>
</template>
<template v-slot:meta v-if="chips.positive">
<div class="inline-flex flex-wrap text-xs -mx-1">
<div class="flex-1">
<div class="px-1 text-green-600 font-medium whitespace-nowrap">Low: &lt; 2%</div>
<div class="px-1 text-yellow-600 font-medium whitespace-nowrap">Moderate &lt; 5%</div>
</div>
<div class="flex-1">
<div class="px-1 text-orange-600 font-medium whitespace-nowrap">High &lt; 20%</div>
<div class="px-1 text-red-700 font-medium whitespace-nowrap">Very high &gt; 20%</div>
</div>
</div>
<a class="link block mt-2 text-xs" href="https://www.who.int/publications/i/item/considerations-in-adjusting-public-health-and-social-measures-in-the-context-of-covid-19-interim-guidanceLow">
Source, see p. 18
</a>
</template>
</StatCard>
</template>
<script setup>
import { reactive, computed } from 'vue'
import { col } from '@/components/charts/util'
import cache from '@/data/cache.js'
const data = cache.data
const chips = reactive({
total_tests: computed(() => {
if (!data.value) return null
const r = data.value.rows.at(-1)
return col(data, r, 'combined_performed_running_total')
}),
tests: computed(() => {
if (!data.value) return null
return data.value.rows.reduce((prev, r) => {
const date = new Date(col(data, r, 'report_date'))
const value = +col(data, r, 'combined_performed')
if (prev && prev.value > value) return prev
else return { date, value }
})
}),
positive: computed(() => {
if (!data.value) return null
return data.value.rows.reduce((prev, r) => {
const date = new Date(col(data, r, 'report_date'))
const value = +col(data, r, 'combined_positive')
if (prev && prev.value > value) return prev
else return { date, value }
})
}),
percent_positive: computed(() => {
if (!data.value) return null
const r = data.value.rows.at(-1)
return col(data, r, 'seven_day_percent_positive')
})
})
</script>

View File

@@ -0,0 +1,68 @@
<template>
<Card>
<h2 class="mb-6 text-xl text-indigo-900 flex justify-between items-center">
Daily Positive Antigen Results
<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 cache from '@/data/cache.js'
const chart = ref('calendar')
const column = 'antigen_positive'
const parameters = cache.parameters
const data = cache.data
const rows = computed(() => {
if (!data.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.end,
data: rows.value.map(r => ([
`${col(data, r, 'report_date')} 12:00:00`,
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')} 12:00:00`,
col(data, r, column)
]))
}
]
}
})
</script>

View File

@@ -0,0 +1,67 @@
<template>
<Card>
<h2 class="mb-6 text-xl text-indigo-900 flex justify-between items-center">
Daily Antigen Tests Performed
<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 cache from '@/data/cache.js'
const chart = ref('calendar')
const column = 'antigen_performed'
const parameters = cache.parameters
const data = cache.data
const rows = computed(() => {
if (!data.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.end,
data: rows.value.map(r => ([
`${col(data, r, 'report_date')} 12:00:00`,
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')} 12:00:00`,
col(data, r, column)
]))
}
]
}
})
</script>

View File

@@ -0,0 +1,68 @@
<template>
<Card>
<h2 class="mb-6 text-xl text-indigo-900 flex justify-between items-center">
Daily Positive PCR Results
<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 cache from '@/data/cache.js'
const chart = ref('calendar')
const column = 'pcr_positive'
const parameters = cache.parameters
const data = cache.data
const rows = computed(() => {
if (!data.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.end,
data: rows.value.map(r => ([
`${col(data, r, 'report_date')} 12:00:00`,
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')} 12:00:00`,
col(data, r, column)
]))
}
]
}
})
</script>

View File

@@ -0,0 +1,68 @@
<template>
<Card>
<h2 class="mb-6 text-xl text-indigo-900 flex justify-between items-center">
Daily PCR Tests Performed
<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 cache from '@/data/cache.js'
const chart = ref('calendar')
const column = 'pcr_performed'
const parameters = cache.parameters
const data = cache.data
const rows = computed(() => {
if (!data.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.end,
data: rows.value.map(r => ([
`${col(data, r, 'report_date')} 12:00:00`,
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')} 12:00:00`,
col(data, r, column)
]))
}
]
}
})
</script>

View File

@@ -0,0 +1,39 @@
<template></template>
<script setup>
import { watch, onMounted } from 'vue'
import cache from '@/data/cache.js'
const parameters = cache.parameters
const data = cache.data
function formatDate(d) {
return [
d.getFullYear(),
('0' + (d.getMonth() + 1)).slice(-2),
('0' + d.getDate()).slice(-2)
].join('-');
}
const today = new Date()
const end = new Date()
const start = new Date(today.valueOf() - 2592000000)
console.log(start, end)
parameters.value = {
county: 'All',
start: formatDate(start),
end: formatDate(end),
}
async function refreshData() {
data.value = await fetch(`/data/testing-trend/${parameters.value.county}.json`).then(res => res.json())
}
watch(() => parameters.value.county, () => {
refreshData()
})
onMounted(() => refreshData())
</script>

8
src/data/cache.js Normal file
View File

@@ -0,0 +1,8 @@
import { reactive, ref, watch } from 'vue'
const cache = {
parameters: ref(null),
data: ref(null)
}
export default cache

View File

@@ -13,11 +13,6 @@
<icon-mdi-test-tube class="mr-2"></icon-mdi-test-tube> testing?
</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">
<icon-mdi-percent-outline class="mr-2"></icon-mdi-percent-outline> test positivity?
</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">
<icon-mdi-virus-outline class="mr-2"></icon-mdi-virus-outline> cases?

View File

@@ -1,4 +1,5 @@
---
title: Introduction
---
# Introduction

View File

@@ -1,24 +0,0 @@
<template><slot></slot></template>
<script setup>
import { onMounted, reactive, ref, watch, provide } from 'vue'
const parameters = reactive({
county: 'All',
start: '2021-01-01',
end: '2021-12-12',
test: () => 'test'
})
const data = ref(null)
provide('parameters', parameters)
provide('data', data)
async function refreshData() {
data.value = await fetch(`/data/testing-trend/${parameters.county}.json`).then(res => res.json())
}
watch(() => parameters.county, () => refreshData())
onMounted(() => refreshData())
</script>

View File

@@ -1,30 +1,33 @@
---
title: What is the statewide trend in testing?
---
import DataSetup from './DataSetup.vue'
<Card>
<Card prose={true}>
# What is the statewide trend in testing?
## What is this report useful for?
The number of new cases reported doesn't always directly indicate how rapidly the disease is spreading.
The number of new cases reported doesn't directly indicate how rapidly the disease is spreading.
By checking how many people are getting tested, you can get an idea of whether the current case load indicates major community spread or
if there is just a significant number of people getting tested for one reason or another.
if there is simply an unusually large number of people getting tested for one reason or another.
**For example:**<br/>
If you get **10** positive results from **100** tests, the disease is probably spreading more widely than would be indicated if there were **50** positive results from **1,000** tests.
This discrepancy is often apparent on Mondays, where tests and cases from over the weekend might wind up in Monday's report.
See also: "<RouterLink to={{path: '/state/test-positivity/'}}>What is the statewide trend in test positivity?</RouterLink>" for a direct comparison of test-to-case rates.
</Card>
<DataSetup client:load>
<SliceSelector></SliceSelector>
<TestingDataSetup client:load></TestingDataSetup>
<Card>
## Number of PCR Tests Performed Each Day
<TestingTrend column="pcr_performed" ></TestingTrend>
</Card>
</DataSetup>
<SliceSelector client:load/>
<div class="grid gap-4 grid-cols-1 sm:grid-cols-2 xl:grid-cols-4">
<ChipsTesting client:load/>
</div>
<div class="grid gap-4 grid-cols-1 xl:grid-cols-2">
<TrendPCRTests client:load/>
<TrendPCRPositive client:load/>
<TrendAntigenTests client:load/>
<TrendAntigenPositive client:load/>
</div>