Add summary section to index.
This commit is contained in:
7
LICENSE.md
Normal file
7
LICENSE.md
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
Copyright 2022 Joshua Bemenderfer <josh@thederf.com>
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||||
96
README.md
96
README.md
@@ -1 +1,95 @@
|
|||||||
# TODO: Write
|
# About
|
||||||
|
|
||||||
|
This site exists to provide a simplified, targeted view into information provided by the [Georgia Department of Health (DPH)](https://dph.georgia.gov/covid-19-daily-status-report) on the COVID-19 pandemic.
|
||||||
|
|
||||||
|
While the DPH provides an excellent range of datasets and visualizations, how to understand their utility and interpret them is left as an exercise to the reader.<br/>
|
||||||
|
Here I've attempted to provide explanations of the utility of certain statistics as well as provide some new visualizations not currently provided by the Department of Health dashboard.
|
||||||
|
|
||||||
|
## Disclaimer
|
||||||
|
|
||||||
|
The data provided on this site is based on information made available by the Georgia Department of Health at the time of reporting.
|
||||||
|
It may at times lack accuracy due to unreported, delayed, or improperly processed information.
|
||||||
|
Reporting time and quality for certain statistics may vary depending on the processes of individual organizations and jurisdictions.
|
||||||
|
|
||||||
|
This site is manually built from daily DPH reports. I will attempt to have new data incorporated by 5:00 PM on weekdays. Updates may be delayed due to personal circumstances.
|
||||||
|
|
||||||
|
**For case reports**, at-home rapid tests are not accounted for. Case numbers may be much higher than reported if [test positivity](https://ga-covid.thederf.com/overall/testing) is unusually high.
|
||||||
|
|
||||||
|
## The following additional reports are planned
|
||||||
|
|
||||||
|
- **What is the overall trend in vaccinations?** - [Data available here](https://experience.arcgis.com/experience/3d8eea39f5c1443db1743a4cb8948a9c) as XLSX, pending processing.
|
||||||
|
- **Who is most at risk by ethnicity and sex?** - Data available in daily reports, pending processing.
|
||||||
|
- **Who is most at risk by vaccination status?** - Weekly reports on breakthrough cases and deaths [are available here](https://dph.georgia.gov/covid-19-breakthrough-reports), but challenging to process.
|
||||||
|
- **How many hospital resources are available?** - [Data available here](https://www.arcgis.com/apps/opsdashboard/index.html#/47c1cee4d02542bea35bc3324d6cf5e3) but requires querying ArcGIS directly.
|
||||||
|
- **How many hospital patients have COVID?** - [Data available here](https://www.arcgis.com/apps/opsdashboard/index.html#/e40c39564f724af7bfe8fd5d88deadb6) but requires querying ArcGIS directly.
|
||||||
|
|
||||||
|
**Please note:** *This is a citizen science project and is not affiliated with or endorsed in any way by the State of Georgia.*
|
||||||
|
|
||||||
|
## Data Sources
|
||||||
|
|
||||||
|
- Georgia Department of Health, COVID-19 Daily Status Report - [https://ga-covid19.ondemand.sas.com/docs/ga_covid_data.zip](https://ga-covid19.ondemand.sas.com/docs/ga_covid_data.zip)
|
||||||
|
- **Update Frequency** - The dataset above is updated each working day around 2:50 PM. I load it onto this site and rebuild shortly thereafter.
|
||||||
|
- **Additional Data** - Some reports on this site may derive additional time-based information by comparing totals from multiple daily reports.
|
||||||
|
- David Eldersveld, TopoJSON Collection, Georgia Counties TopoJSON - [https://github.com/deldersveld/topojson/blob/master/countries/us-states/GA-13-georgia-counties.json](https://github.com/deldersveld/topojson/blob/master/countries/us-states/GA-13-georgia-counties.json)
|
||||||
|
- Tweaked and used for rendering county maps.
|
||||||
|
|
||||||
|
## Developing
|
||||||
|
|
||||||
|
Feel free to build it yourself, take a look around, validate the reports, and [suggest improvements](#contact).<br/>
|
||||||
|
*Note, the code is still very much in the "make it work" stage, and has much room for being simplified, abstracted, and cleaned up. Ignore the fact that I'm using TypeScript as if it were un-typed JavaScript.*
|
||||||
|
|
||||||
|
### Stack
|
||||||
|
|
||||||
|
- [îles](https://iles-docs.netlify.app/) - Generates static pages from MDX files and Vue components, with optional component-level client-side hydration.
|
||||||
|
- Excellent for getting a site built with a modern tech stack and minimal extraneous config. Still in beta with some rough edges, but I greatly enjoyed working with it.
|
||||||
|
- [Vue 3](https://v3.vuejs.org/) - Used for layout and client-side components such as charts.
|
||||||
|
- [JSCharting](https://jscharting.com/) - Used for charts. Straightforward to get up and running with, but provides a vast array of data visualization options.
|
||||||
|
- [TailwindCSS](https://tailwindcss.com/) - Because people keep telling me to try it. I get the appeal. It made style tweaking incredibly fast and fluid, but it definitely results in messy markup.
|
||||||
|
|
||||||
|
### Building & Running
|
||||||
|
|
||||||
|
1. Clone repository using [git](https://git-scm.com/)
|
||||||
|
```bash
|
||||||
|
$ git clone https://git.thederf.com/thederf/ga-covid.thederf.com.git
|
||||||
|
```
|
||||||
|
2. Install dependencies using [npm](https://www.npmjs.com/) or your preferred [node.js](https://nodejs.org) package manager of choice.
|
||||||
|
```bash
|
||||||
|
$ cd ga-covid.thederf.com/
|
||||||
|
$ npm install
|
||||||
|
```
|
||||||
|
3. (Optional) Rebuild the data json files.
|
||||||
|
```bash
|
||||||
|
npm run process:data
|
||||||
|
```
|
||||||
|
4. Run a local server for development
|
||||||
|
```bash
|
||||||
|
npm run dev
|
||||||
|
```
|
||||||
|
5. Build static site
|
||||||
|
```bash
|
||||||
|
npm run build
|
||||||
|
```
|
||||||
|
|
||||||
|
### File Tree
|
||||||
|
|
||||||
|
```txt
|
||||||
|
data/ - Scripts for comverting the raw CSV source files into browser-friendly JSON, removing extraneous data points, and segmenting data into separate files.
|
||||||
|
- parser.js - Data parser entrypoint.
|
||||||
|
parser/... - Scripts for producing json files for specific reports.
|
||||||
|
raw/
|
||||||
|
- YYYY-MM-DD.zip - Raw Georgia DPH datasets from the link above, by date downloaded.
|
||||||
|
Including historical datasets allows for building time-series reports for data provided exclusively in daily counts.
|
||||||
|
...
|
||||||
|
public/ - Static assets.
|
||||||
|
maps/ - GeoJSON map files for rendering maps.
|
||||||
|
data/ - Generated JSON files prepared for browser loading by the parser above.
|
||||||
|
...
|
||||||
|
src/
|
||||||
|
assets/ - CSS & Image assets processed by Vite
|
||||||
|
components/ - Vue components for rendering client-interactive parts of the website.
|
||||||
|
charts/ - Components that handle chart & chip rendering.
|
||||||
|
layouts/ - Vue components for laying out the site. Primarily server-rendered.
|
||||||
|
pages/ - MDX files for site pages.
|
||||||
|
...
|
||||||
|
...
|
||||||
|
```
|
||||||
|
|||||||
13
components.d.ts
vendored
13
components.d.ts
vendored
@@ -10,16 +10,27 @@ declare module 'vue' {
|
|||||||
ChipsDeaths: typeof import('./src/components/charts/overall/deaths/ChipsDeaths.vue')['default']
|
ChipsDeaths: typeof import('./src/components/charts/overall/deaths/ChipsDeaths.vue')['default']
|
||||||
ChipsHospitalizations: typeof import('./src/components/charts/overall/hospitalizations/ChipsHospitalizations.vue')['default']
|
ChipsHospitalizations: typeof import('./src/components/charts/overall/hospitalizations/ChipsHospitalizations.vue')['default']
|
||||||
ChipsRiskAge: typeof import('./src/components/charts/risk/age/ChipsRiskAge.vue')['default']
|
ChipsRiskAge: typeof import('./src/components/charts/risk/age/ChipsRiskAge.vue')['default']
|
||||||
|
ChipsRiskHealthConditions: typeof import('./src/components/charts/risk/health-conditions/ChipsRiskHealthConditions.vue')['default']
|
||||||
|
ChipsSummary: typeof import('./src/components/charts/summary/ChipsSummary.vue')['default']
|
||||||
ChipsTesting: typeof import('./src/components/charts/overall/testing/ChipsTesting.vue')['default']
|
ChipsTesting: typeof import('./src/components/charts/overall/testing/ChipsTesting.vue')['default']
|
||||||
County_time_series: typeof import('./src/components/charts/state/cases/county_time_series.js')['default']
|
County_time_series: typeof import('./src/components/charts/state/cases/county_time_series.js')['default']
|
||||||
County_time_series_store: typeof import('./src/components/charts/state/cases/county_time_series_store.js')['default']
|
County_time_series_store: typeof import('./src/components/charts/state/cases/county_time_series_store.js')['default']
|
||||||
IconHealthiconsClinicalFOutline: typeof import('~icons/healthicons/clinical-f-outline')['default']
|
IconHealthiconsClinicalFOutline: typeof import('~icons/healthicons/clinical-f-outline')['default']
|
||||||
|
IconHealthiconsHospitalized: typeof import('~icons/healthicons/hospitalized')['default']
|
||||||
|
IconHealthiconsHospitalizedOutline: typeof import('~icons/healthicons/hospitalized-outline')['default']
|
||||||
|
IconHealthiconsVentilator: typeof import('~icons/healthicons/ventilator')['default']
|
||||||
|
IconHealthiconsVentilatorAl: typeof import('~icons/healthicons/ventilator-al')['default']
|
||||||
|
IconHealthiconsVentilatorAlt: typeof import('~icons/healthicons/ventilator-alt')['default']
|
||||||
|
IconHealthiconsVentilatorOutline: typeof import('~icons/healthicons/ventilator-outline')['default']
|
||||||
IconMdiGenderMaleFemale: typeof import('~icons/mdi/gender-male-female')['default']
|
IconMdiGenderMaleFemale: typeof import('~icons/mdi/gender-male-female')['default']
|
||||||
IconMdiGraveStone: typeof import('~icons/mdi/grave-stone')['default']
|
IconMdiGraveStone: typeof import('~icons/mdi/grave-stone')['default']
|
||||||
IconMdiHospitalBoxOutline: typeof import('~icons/mdi/hospital-box-outline')['default']
|
IconMdiHospitalBoxOutline: typeof import('~icons/mdi/hospital-box-outline')['default']
|
||||||
IconMdiHumanCane: typeof import('~icons/mdi/human-cane')['default']
|
IconMdiHumanCane: typeof import('~icons/mdi/human-cane')['default']
|
||||||
IconMdiPercentOutline: typeof import('~icons/mdi/percent-outline')['default']
|
IconMdiPercentOutline: typeof import('~icons/mdi/percent-outline')['default']
|
||||||
|
IconMdiPerson: typeof import('~icons/mdi/person')['default']
|
||||||
|
IconMdiSyringe: typeof import('~icons/mdi/syringe')['default']
|
||||||
IconMdiTestTube: typeof import('~icons/mdi/test-tube')['default']
|
IconMdiTestTube: typeof import('~icons/mdi/test-tube')['default']
|
||||||
|
IconMdiUser: typeof import('~icons/mdi/user')['default']
|
||||||
IconMdiVirusOutline: typeof import('~icons/mdi/virus-outline')['default']
|
IconMdiVirusOutline: typeof import('~icons/mdi/virus-outline')['default']
|
||||||
IconMdiWeb: typeof import('~icons/mdi/web')['default']
|
IconMdiWeb: typeof import('~icons/mdi/web')['default']
|
||||||
Island: typeof import('./node_modules/iles/dist/client/app/components/Island.vue')['default']
|
Island: typeof import('./node_modules/iles/dist/client/app/components/Island.vue')['default']
|
||||||
@@ -37,7 +48,7 @@ declare module 'vue' {
|
|||||||
RiskAgeParameters: typeof import('./src/components/charts/risk/age/RiskAgeParameters.vue')['default']
|
RiskAgeParameters: typeof import('./src/components/charts/risk/age/RiskAgeParameters.vue')['default']
|
||||||
SliceSelector: typeof import('./src/components/SliceSelector.vue')['default']
|
SliceSelector: typeof import('./src/components/SliceSelector.vue')['default']
|
||||||
StatCard: typeof import('./src/components/cards/StatCard.vue')['default']
|
StatCard: typeof import('./src/components/cards/StatCard.vue')['default']
|
||||||
Store: typeof import('./src/components/charts/overall/cases/store.js')['default']
|
Store: typeof import('./src/components/charts/summary/store.js')['default']
|
||||||
Store_combined: typeof import('./src/components/charts/overall/cases/store_combined.js')['default']
|
Store_combined: typeof import('./src/components/charts/overall/cases/store_combined.js')['default']
|
||||||
TestingDataSetup: typeof import('./src/components/pages/state/TestingDataSetup.vue')['default']
|
TestingDataSetup: typeof import('./src/components/pages/state/TestingDataSetup.vue')['default']
|
||||||
TestingParameters: typeof import('./src/components/charts/state/testing/TestingParameters.vue')['default']
|
TestingParameters: typeof import('./src/components/charts/state/testing/TestingParameters.vue')['default']
|
||||||
|
|||||||
@@ -11,6 +11,8 @@ import OverallDeaths from './parser/overall/deaths.js'
|
|||||||
import RiskAge from './parser/risk/age.js'
|
import RiskAge from './parser/risk/age.js'
|
||||||
import RiskHealthConditions from './parser/risk/health-conditions.js'
|
import RiskHealthConditions from './parser/risk/health-conditions.js'
|
||||||
|
|
||||||
|
import Summary from './parser/summary.js'
|
||||||
|
|
||||||
async function main() {
|
async function main() {
|
||||||
const sources = await fg(['./data/raw/*.zip'])
|
const sources = await fg(['./data/raw/*.zip'])
|
||||||
sources.sort()
|
sources.sort()
|
||||||
@@ -29,6 +31,8 @@ async function main() {
|
|||||||
|
|
||||||
await RiskAge(zips)
|
await RiskAge(zips)
|
||||||
await RiskHealthConditions(zips)
|
await RiskHealthConditions(zips)
|
||||||
|
|
||||||
|
await Summary()
|
||||||
}
|
}
|
||||||
|
|
||||||
main()
|
main()
|
||||||
|
|||||||
41
data/parser/summary.js
Normal file
41
data/parser/summary.js
Normal file
@@ -0,0 +1,41 @@
|
|||||||
|
|
||||||
|
import mkdirp from 'mkdirp'
|
||||||
|
import path from 'path'
|
||||||
|
import fsp from 'fs/promises'
|
||||||
|
|
||||||
|
async function readJSON(file) {
|
||||||
|
return JSON.parse(await fsp.readFile(file, 'utf-8'))
|
||||||
|
}
|
||||||
|
|
||||||
|
export default async function process () {
|
||||||
|
const testing = await readJSON('./public/data/overall/testing/by-county/-- All --.json')
|
||||||
|
const cases = await readJSON('./public/data/overall/cases/by-county/-- All --.json')
|
||||||
|
const hospitalizations = await readJSON('./public/data/overall/hospitalizations/by-county/-- All --.json')
|
||||||
|
const deaths = await readJSON('./public/data/overall/deaths/by-county/-- All --.json')
|
||||||
|
|
||||||
|
const summary = {
|
||||||
|
last_report_date: testing.rows.at(-1)[0],
|
||||||
|
testing: {
|
||||||
|
headers: testing.headers,
|
||||||
|
current: testing.rows.at(-1),
|
||||||
|
prev: testing.rows.at(-2),
|
||||||
|
},
|
||||||
|
cases: {
|
||||||
|
headers: cases.headers,
|
||||||
|
current: cases.rows.at(-1),
|
||||||
|
prev: cases.rows.at(-2),
|
||||||
|
},
|
||||||
|
hospitalizations: {
|
||||||
|
headers: hospitalizations.headers,
|
||||||
|
current: hospitalizations.rows.at(-1),
|
||||||
|
prev: hospitalizations.rows.at(-2),
|
||||||
|
},
|
||||||
|
deaths: {
|
||||||
|
headers: deaths.headers,
|
||||||
|
current: deaths.rows.at(-1),
|
||||||
|
prev: deaths.rows.at(-2),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
await fsp.writeFile('./public/data/summary.json', JSON.stringify(summary))
|
||||||
|
}
|
||||||
@@ -1,6 +1,7 @@
|
|||||||
{
|
{
|
||||||
"name": "ga-covid-charts",
|
"name": "ga-covid-charts",
|
||||||
"private": true,
|
"private": true,
|
||||||
|
"license": "MIT",
|
||||||
"version": "0.0.0",
|
"version": "0.0.0",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"dev": "iles dev",
|
"dev": "iles dev",
|
||||||
|
|||||||
1
public/data/summary.json
Normal file
1
public/data/summary.json
Normal file
@@ -0,0 +1 @@
|
|||||||
|
{"last_report_date":"2021-12-31","testing":{"headers":["report_date","pcr_performed","pcr_positive","antigen_performed","antigen_positive","combined_performed","combined_positive","seven_day_percent_positive","combined_performed_running_total"],"current":["2021-12-31",54958,21204,27064,6629,82022,27833,32.1,18485118],"prev":["2021-12-30",57566,21851,31395,7521,88961,29372,30,18403096]},"cases":{"headers":["report_date","population","cases","cases_per_capita","cases_14_days","case_rate_14_days","total_cases","total_cases_per_capita"],"current":["2021-12-31",10833472,17641,0.0016283791567467936,null,null,1839082,0.16975924246631183],"prev":["2021-12-30",10833472,17923,0.0016544095927879816,null,null,1814762,0.16751434812403632]},"hospitalizations":{"headers":["report_date","population","hospitalizations","hospitalizations_per_capita","hospitalizations_last_14_days","hospitalizations_last_14_days_per_capita"],"current":["2021-12-31",10833472,248,0.00002289201467451986,2747,0.0002535659851246212],"prev":["2021-12-30",10833472,283,0.000026122742551972257,2590,0.00023907386293147754]},"deaths":{"headers":["report_date","population","deaths","deaths_per_capita","deaths_14_days","death_rate_14_days","total_deaths","total_deaths_per_capita"],"current":["2021-12-31",10833472,18,0.0000016615171941183769,340,0.00003138421366668045,26425,0.0024391995474765614],"prev":["2021-12-30",10833472,49,0.000004523019028433359,407,0.00003756874988923219,26407,0.002437538030282443]}}
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
<template>
|
<template>
|
||||||
<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-violet-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-violet-900 prose-h1:mb-0 prose-a:text-violet-700 prose-a:font-normal' : '']">
|
||||||
<slot></slot>
|
<slot></slot>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|||||||
@@ -1,26 +1,26 @@
|
|||||||
<template>
|
<template>
|
||||||
<StatCard>
|
<StatCard>
|
||||||
<template v-slot:heading>Confirmed Cases {{last_report_date ? humanDate(last_report_date) : ''}}</template>
|
<template v-slot:heading>Confirmed Cases {{last_report_date ? humanDate(last_report_date) : ''}}</template>
|
||||||
<template v-slot:value v-if="chips.today_cases != null">
|
<template v-slot:value v-if="chips.current_cases != null">
|
||||||
{{chips.today_cases.toLocaleString()}}
|
{{chips.current_cases.toLocaleString()}}
|
||||||
</template>
|
</template>
|
||||||
<template v-slot:meta v-if="chips.today_case_change != null">
|
<template v-slot:meta v-if="chips.current_case_change != null">
|
||||||
<span v-if="chips.today_case_change > 0">Up </span>
|
<span v-if="chips.current_case_change > 0">Up </span>
|
||||||
<span v-if="chips.today_case_change < 0">Down </span>
|
<span v-if="chips.current_case_change < 0">Down </span>
|
||||||
<span :class="{'font-semibold': true, 'text-red-600': chips.today_case_change > 0, 'text-green-600': chips.today_case_change < 0}">
|
<span :class="{'font-semibold': true, 'text-red-600': chips.current_case_change > 0, 'text-green-600': chips.current_case_change < 0}">
|
||||||
{{Math.abs(chips.today_case_change).toLocaleString()}} ({{chips.today_case_change_percent}}%)
|
{{Math.abs(chips.current_case_change).toLocaleString()}} ({{chips.current_case_change_percent}}%)
|
||||||
</span> compared to previous day
|
</span> compared to previous day
|
||||||
</template>
|
</template>
|
||||||
</StatCard>
|
</StatCard>
|
||||||
|
|
||||||
<StatCard>
|
<StatCard>
|
||||||
<template v-slot:heading>Cases per 10,000 Residents {{last_report_date ? humanDate(last_report_date) : ''}}</template>
|
<template v-slot:heading>Cases per 10,000 Residents {{last_report_date ? humanDate(last_report_date) : ''}}</template>
|
||||||
<template v-slot:value v-if="chips.today_cases_per_capita != null">
|
<template v-slot:value v-if="chips.current_cases_per_capita != null">
|
||||||
<span v-if="chips.population">~{{chips.today_cases_per_capita.toLocaleString()}}</span>
|
<span v-if="chips.population">~{{chips.current_cases_per_capita.toLocaleString()}}</span>
|
||||||
<span v-else>No Data</span>
|
<span v-else>No Data</span>
|
||||||
</template>
|
</template>
|
||||||
<template v-slot:meta v-if="chips.population">
|
<template v-slot:meta v-if="chips.population">
|
||||||
<span class="text-violet-500 font-semibold">{{chips.today_cases.toLocaleString()}}</span> out of <span class="text-violet-500 font-semibold">{{chips.population.toLocaleString()}}</span> residents
|
<span class="text-violet-500 font-semibold">{{chips.current_cases.toLocaleString()}}</span> out of <span class="text-violet-500 font-semibold">{{chips.population.toLocaleString()}}</span> residents
|
||||||
</template>
|
</template>
|
||||||
</StatCard>
|
</StatCard>
|
||||||
|
|
||||||
@@ -68,14 +68,14 @@ const chips = reactive({
|
|||||||
return col(data, r, 'population')
|
return col(data, r, 'population')
|
||||||
}),
|
}),
|
||||||
|
|
||||||
today_cases: computed(() => {
|
current_cases: computed(() => {
|
||||||
if (!data.value) return null
|
if (!data.value) return null
|
||||||
|
|
||||||
const r = data.value.rows.at(-1)
|
const r = data.value.rows.at(-1)
|
||||||
return col(data, r, 'cases')
|
return col(data, r, 'cases')
|
||||||
}),
|
}),
|
||||||
|
|
||||||
today_case_change: computed(() => {
|
current_case_change: computed(() => {
|
||||||
if (!data.value) return null
|
if (!data.value) return null
|
||||||
|
|
||||||
const today = col(data, data.value.rows.at(-1), 'cases')
|
const today = col(data, data.value.rows.at(-1), 'cases')
|
||||||
@@ -83,7 +83,7 @@ const chips = reactive({
|
|||||||
return today - yesterday
|
return today - yesterday
|
||||||
}),
|
}),
|
||||||
|
|
||||||
today_case_change_percent: computed(() => {
|
current_case_change_percent: computed(() => {
|
||||||
if (!data.value) return null
|
if (!data.value) return null
|
||||||
|
|
||||||
const today = col(data, data.value.rows.at(-1), 'cases')
|
const today = col(data, data.value.rows.at(-1), 'cases')
|
||||||
@@ -100,7 +100,7 @@ const chips = reactive({
|
|||||||
return Math.abs(percent.toFixed(1))
|
return Math.abs(percent.toFixed(1))
|
||||||
}),
|
}),
|
||||||
|
|
||||||
today_cases_per_capita: computed(() => {
|
current_cases_per_capita: computed(() => {
|
||||||
if (!data.value) return null
|
if (!data.value) return null
|
||||||
|
|
||||||
const r = data.value.rows.at(-1)
|
const r = data.value.rows.at(-1)
|
||||||
|
|||||||
@@ -28,8 +28,6 @@ watch(() => store.parameters.value.county, () => {
|
|||||||
refreshData()
|
refreshData()
|
||||||
})
|
})
|
||||||
|
|
||||||
if (globalThis.window) {
|
if (globalThis.window) refreshData()
|
||||||
refreshData()
|
|
||||||
}
|
|
||||||
|
|
||||||
export default store
|
export default store
|
||||||
|
|||||||
@@ -8,8 +8,6 @@ async function refreshData() {
|
|||||||
store.data.value = await fetch(`/data/overall/cases/combined.json`).then(res => res.json())
|
store.data.value = await fetch(`/data/overall/cases/combined.json`).then(res => res.json())
|
||||||
}
|
}
|
||||||
|
|
||||||
if (globalThis.window) {
|
if (globalThis.window) refreshData()
|
||||||
refreshData()
|
|
||||||
}
|
|
||||||
|
|
||||||
export default store
|
export default store
|
||||||
|
|||||||
@@ -28,8 +28,6 @@ watch(() => store.parameters.value.county, () => {
|
|||||||
refreshData()
|
refreshData()
|
||||||
})
|
})
|
||||||
|
|
||||||
if (globalThis.window) {
|
if (globalThis.window) refreshData()
|
||||||
refreshData()
|
|
||||||
}
|
|
||||||
|
|
||||||
export default store
|
export default store
|
||||||
|
|||||||
@@ -8,8 +8,6 @@ async function refreshData() {
|
|||||||
store.data.value = await fetch(`/data/overall/deaths/combined.json`).then(res => res.json())
|
store.data.value = await fetch(`/data/overall/deaths/combined.json`).then(res => res.json())
|
||||||
}
|
}
|
||||||
|
|
||||||
if (globalThis.window) {
|
if (globalThis.window) refreshData()
|
||||||
refreshData()
|
|
||||||
}
|
|
||||||
|
|
||||||
export default store
|
export default store
|
||||||
|
|||||||
@@ -28,8 +28,6 @@ watch(() => store.parameters.value.county, () => {
|
|||||||
refreshData()
|
refreshData()
|
||||||
})
|
})
|
||||||
|
|
||||||
if (globalThis.window) {
|
if (globalThis.window) refreshData()
|
||||||
refreshData()
|
|
||||||
}
|
|
||||||
|
|
||||||
export default store
|
export default store
|
||||||
|
|||||||
@@ -5,11 +5,9 @@ const store = {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async function refreshData() {
|
async function refreshData() {
|
||||||
store.data.value = await fetch(`/data/state/hospitalizations/combined.json`).then(res => res.json())
|
store.data.value = await fetch(`/data/overall/hospitalizations/combined.json`).then(res => res.json())
|
||||||
}
|
}
|
||||||
|
|
||||||
if (globalThis.window) {
|
if (globalThis.window) refreshData()
|
||||||
refreshData()
|
|
||||||
}
|
|
||||||
|
|
||||||
export default store
|
export default store
|
||||||
|
|||||||
@@ -30,7 +30,7 @@
|
|||||||
</StatCard>
|
</StatCard>
|
||||||
|
|
||||||
<StatCard>
|
<StatCard>
|
||||||
<template v-slot:heading>7-Day Percent Positive</template>
|
<template v-slot:heading>7-Day Test Positivity</template>
|
||||||
<template v-slot:value v-if="chips.percent_positive">
|
<template v-slot:value v-if="chips.percent_positive">
|
||||||
<span :class="{
|
<span :class="{
|
||||||
'text-green-600': chips.percent_positive < 2,
|
'text-green-600': chips.percent_positive < 2,
|
||||||
@@ -41,7 +41,7 @@
|
|||||||
{{chips.percent_positive}}<span class="font-light">%</span>
|
{{chips.percent_positive}}<span class="font-light">%</span>
|
||||||
</span>
|
</span>
|
||||||
</template>
|
</template>
|
||||||
<template v-slot:meta v-if="chips.positive">
|
<template v-slot:meta v-if="chips.percent_positive">
|
||||||
<div class="inline-flex flex-wrap text-xs -mx-1">
|
<div class="inline-flex flex-wrap text-xs -mx-1">
|
||||||
<div class="flex-1">
|
<div class="flex-1">
|
||||||
<div class="px-1 text-green-600 font-medium whitespace-nowrap">Low: < 2%</div>
|
<div class="px-1 text-green-600 font-medium whitespace-nowrap">Low: < 2%</div>
|
||||||
|
|||||||
@@ -22,8 +22,6 @@ watch(() => store.parameters.value.county, () => {
|
|||||||
refreshData()
|
refreshData()
|
||||||
})
|
})
|
||||||
|
|
||||||
if (globalThis.window) {
|
if (globalThis.window) refreshData()
|
||||||
refreshData()
|
|
||||||
}
|
|
||||||
|
|
||||||
export default store
|
export default store
|
||||||
|
|||||||
@@ -23,8 +23,6 @@ async function refreshData() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (globalThis.window) {
|
if (globalThis.window) refreshData()
|
||||||
refreshData()
|
|
||||||
}
|
|
||||||
|
|
||||||
export default store
|
export default store
|
||||||
|
|||||||
@@ -8,8 +8,6 @@ async function refreshData() {
|
|||||||
store.data.value = await fetch(`/data/risk/health-conditions/health-conditions.json`).then(res => res.json())
|
store.data.value = await fetch(`/data/risk/health-conditions/health-conditions.json`).then(res => res.json())
|
||||||
}
|
}
|
||||||
|
|
||||||
if (globalThis.window) {
|
if (globalThis.window) refreshData()
|
||||||
refreshData()
|
|
||||||
}
|
|
||||||
|
|
||||||
export default store
|
export default store
|
||||||
|
|||||||
216
src/components/charts/summary/ChipsSummary.vue
Normal file
216
src/components/charts/summary/ChipsSummary.vue
Normal file
@@ -0,0 +1,216 @@
|
|||||||
|
<template>
|
||||||
|
<StatCard>
|
||||||
|
<template v-slot:heading>Tests Reported {{last_report_date ? humanDate(last_report_date) : ''}}</template>
|
||||||
|
<template v-slot:value v-if="chips.current_testing">
|
||||||
|
{{chips.current_testing.toLocaleString()}}
|
||||||
|
</template>
|
||||||
|
<template v-slot:meta v-if="chips.current_testing">
|
||||||
|
PCR + Antigen
|
||||||
|
</template>
|
||||||
|
<template v-slot:meta v-if="chips.current_testing_change != null">
|
||||||
|
<span v-if="chips.current_testing_change > 0">Up </span>
|
||||||
|
<span v-if="chips.current_testing_change < 0">Down </span>
|
||||||
|
<span class="font-semibold text-violet-600">
|
||||||
|
{{Math.abs(chips.current_testing_change).toLocaleString()}} ({{chips.current_testing_change_percent}}%)
|
||||||
|
</span> compared to previous day
|
||||||
|
</template>
|
||||||
|
</StatCard>
|
||||||
|
|
||||||
|
<StatCard>
|
||||||
|
<template v-slot:heading>7-Day Test Positivity</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.percent_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: < 2%</div>
|
||||||
|
<div class="px-1 text-yellow-600 font-medium whitespace-nowrap">Moderate < 5%</div>
|
||||||
|
</div>
|
||||||
|
<div class="flex-1">
|
||||||
|
<div class="px-1 text-orange-600 font-medium whitespace-nowrap">High < 20%</div>
|
||||||
|
<div class="px-1 text-red-700 font-medium whitespace-nowrap">Very high > 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>
|
||||||
|
|
||||||
|
<StatCard>
|
||||||
|
<template v-slot:heading>Confirmed Cases {{last_report_date ? humanDate(last_report_date) : ''}}</template>
|
||||||
|
<template v-slot:value v-if="chips.current_cases != null">
|
||||||
|
{{chips.current_cases.toLocaleString()}}
|
||||||
|
</template>
|
||||||
|
<template v-slot:meta v-if="chips.current_case_change != null">
|
||||||
|
<span v-if="chips.current_case_change > 0">Up </span>
|
||||||
|
<span v-if="chips.current_case_change < 0">Down </span>
|
||||||
|
<span :class="{'font-semibold': true, 'text-red-600': chips.current_case_change > 0, 'text-green-600': chips.current_case_change < 0}">
|
||||||
|
{{Math.abs(chips.current_case_change).toLocaleString()}} ({{chips.current_case_change_percent}}%)
|
||||||
|
</span> compared to previous day
|
||||||
|
</template>
|
||||||
|
</StatCard>
|
||||||
|
|
||||||
|
<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>Total Confirmed Cases</template>
|
||||||
|
<template v-slot:value v-if="chips.total_cases != null">
|
||||||
|
{{chips.total_cases.toLocaleString()}}
|
||||||
|
</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>
|
||||||
|
</StatCard>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup>
|
||||||
|
import { reactive, computed } from 'vue'
|
||||||
|
import { col, humanDate } from '@/components/charts/util'
|
||||||
|
import store from '@/components/charts/summary/store.js'
|
||||||
|
|
||||||
|
const data = store.data
|
||||||
|
|
||||||
|
const last_report_date = computed(() => {
|
||||||
|
if (!data.value) return null
|
||||||
|
return data.value.last_report_date
|
||||||
|
})
|
||||||
|
|
||||||
|
const chips = reactive({
|
||||||
|
current_testing: computed(() => {
|
||||||
|
if (!data.value) return null
|
||||||
|
|
||||||
|
return col(data.value.testing, data.value.testing.current, 'combined_performed')
|
||||||
|
}),
|
||||||
|
|
||||||
|
current_testing_change: computed(() => {
|
||||||
|
if (!data.value) return null
|
||||||
|
|
||||||
|
const today = col(data.value.testing, data.value.testing.current, 'combined_performed')
|
||||||
|
const yesterday = col(data.value.testing, data.value.testing.prev, 'combined_performed')
|
||||||
|
return today - yesterday
|
||||||
|
}),
|
||||||
|
|
||||||
|
current_testing_change_percent: computed(() => {
|
||||||
|
if (!data.value) return null
|
||||||
|
|
||||||
|
const today = col(data.value.testing, data.value.testing.current, 'combined_performed')
|
||||||
|
const yesterday = col(data.value.testing, data.value.testing.prev, 'combined_performed')
|
||||||
|
const change = today - yesterday
|
||||||
|
|
||||||
|
const percent = change > 0
|
||||||
|
? ((change / yesterday) * 100)
|
||||||
|
: ((change / today) * 100)
|
||||||
|
|
||||||
|
if (Math.abs(percent) === Infinity) return 100
|
||||||
|
if (isNaN(percent)) return 0
|
||||||
|
|
||||||
|
return Math.abs(percent.toFixed(1))
|
||||||
|
}),
|
||||||
|
|
||||||
|
percent_positive: computed(() => {
|
||||||
|
if (!data.value) return null
|
||||||
|
|
||||||
|
return col(data.value.testing, data.value.testing.current, 'seven_day_percent_positive')
|
||||||
|
}),
|
||||||
|
|
||||||
|
current_cases: computed(() => {
|
||||||
|
if (!data.value) return null
|
||||||
|
|
||||||
|
return col(data.value.cases, data.value.cases.current, 'cases')
|
||||||
|
}),
|
||||||
|
|
||||||
|
current_case_change: computed(() => {
|
||||||
|
if (!data.value) return null
|
||||||
|
|
||||||
|
const today = col(data.value.cases, data.value.cases.current, 'cases')
|
||||||
|
const yesterday = col(data.value.cases, data.value.cases.prev, 'cases')
|
||||||
|
return today - yesterday
|
||||||
|
}),
|
||||||
|
|
||||||
|
current_case_change_percent: computed(() => {
|
||||||
|
if (!data.value) return null
|
||||||
|
|
||||||
|
const today = col(data.value.cases, data.value.cases.current, 'cases')
|
||||||
|
const yesterday = col(data.value.cases, data.value.cases.prev, 'cases')
|
||||||
|
const change = today - yesterday
|
||||||
|
|
||||||
|
const percent = change > 0
|
||||||
|
? ((change / yesterday) * 100)
|
||||||
|
: ((change / today) * 100)
|
||||||
|
|
||||||
|
if (Math.abs(percent) === Infinity) return 100
|
||||||
|
if (isNaN(percent)) return 0
|
||||||
|
|
||||||
|
return Math.abs(percent.toFixed(1))
|
||||||
|
}),
|
||||||
|
|
||||||
|
current_deaths: computed(() => {
|
||||||
|
if (!data.value) return null
|
||||||
|
|
||||||
|
return col(data.value.deaths, data.value.deaths.current, 'deaths')
|
||||||
|
}),
|
||||||
|
|
||||||
|
current_death_change: computed(() => {
|
||||||
|
if (!data.value) return null
|
||||||
|
|
||||||
|
const today = col(data.value.deaths, data.value.deaths.current, 'deaths')
|
||||||
|
const yesterday = col(data.value.deaths, data.value.deaths.prev, 'deaths')
|
||||||
|
return today - yesterday
|
||||||
|
}),
|
||||||
|
|
||||||
|
current_death_change_percent: computed(() => {
|
||||||
|
if (!data.value) return null
|
||||||
|
|
||||||
|
const today = col(data.value.deaths, data.value.deaths.current, 'deaths')
|
||||||
|
const yesterday = col(data.value.deaths, data.value.deaths.prev, 'deaths')
|
||||||
|
const change = today - yesterday
|
||||||
|
|
||||||
|
const percent = change > 0
|
||||||
|
? ((change / yesterday) * 100)
|
||||||
|
: ((change / today) * 100)
|
||||||
|
|
||||||
|
if (Math.abs(percent) === Infinity) return 100
|
||||||
|
if (isNaN(percent)) return 0
|
||||||
|
|
||||||
|
return Math.abs(percent.toFixed(1))
|
||||||
|
}),
|
||||||
|
|
||||||
|
total_cases: computed(() => {
|
||||||
|
if (!data.value) return null
|
||||||
|
|
||||||
|
return col(data.value.cases, data.value.cases.current, 'total_cases')
|
||||||
|
}),
|
||||||
|
|
||||||
|
total_deaths: computed(() => {
|
||||||
|
if (!data.value) return null
|
||||||
|
|
||||||
|
return col(data.value.deaths, data.value.deaths.current, 'total_deaths')
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
</script>
|
||||||
13
src/components/charts/summary/store.js
Normal file
13
src/components/charts/summary/store.js
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
import { reactive, ref, watch } from 'vue'
|
||||||
|
|
||||||
|
const store = {
|
||||||
|
data: ref(null)
|
||||||
|
}
|
||||||
|
|
||||||
|
async function refreshData() {
|
||||||
|
store.data.value = await fetch(`/data/summary.json`).then(res => res.json())
|
||||||
|
}
|
||||||
|
|
||||||
|
if (globalThis.window) refreshData()
|
||||||
|
|
||||||
|
export default store
|
||||||
@@ -1,7 +1,7 @@
|
|||||||
export const colors = ['#f5f3ff','#ede9fe','#ddd6fe','#c4b5fd','#a78bfa','#8b5cf6','#7c3aed','#6d28d9','#5b21b6','#4c1d95']
|
export const colors = ['#f5f3ff','#ede9fe','#ddd6fe','#c4b5fd','#a78bfa','#8b5cf6','#7c3aed','#6d28d9','#5b21b6','#4c1d95']
|
||||||
|
|
||||||
export function col(data, row, column) {
|
export function col(data, row, column) {
|
||||||
const index = data.value.headers.indexOf(column)
|
const index = data.headers ? data.headers.indexOf(column) : data.value.headers.indexOf(column)
|
||||||
if (index === -1) return null
|
if (index === -1) return null
|
||||||
return row[index]
|
return row[index]
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
<ul class="space-y-2 pb-2">
|
<ul class="space-y-2 pb-2">
|
||||||
<li>
|
<li>
|
||||||
<router-link :to="{path: '/'}" class="text-base text-violet-900 font-bold flex flex-col rounded-lg p-2 px-3 hover:bg-violet-100 group" active-class="bg-violet-800 text-white hover:bg-violet-800">
|
<router-link :to="{path: '/'}" class="text-base text-violet-900 font-bold flex flex-col rounded-lg p-2 px-3 hover:bg-violet-100 group" active-class="bg-violet-800 text-white hover:bg-violet-800">
|
||||||
Introduction
|
Home
|
||||||
</router-link>
|
</router-link>
|
||||||
</li>
|
</li>
|
||||||
<li>
|
<li>
|
||||||
@@ -28,35 +28,16 @@
|
|||||||
<icon-mdi-grave-stone class="mr-2"></icon-mdi-grave-stone> deaths?
|
<icon-mdi-grave-stone class="mr-2"></icon-mdi-grave-stone> deaths?
|
||||||
</router-link>
|
</router-link>
|
||||||
</li>
|
</li>
|
||||||
|
<li class="flex items-center justify-between">
|
||||||
|
<a href="#" class="text-base rounded-lg flex items-center p-2 mr-3 group opacity-50">
|
||||||
|
<icon-mdi-syringe class="mr-2"></icon-mdi-syringe> vaccinations?
|
||||||
|
</a>
|
||||||
|
<span class="rounded-lg bg-violet-700 text-white text-sm px-2 py-1 mr-3">Planned</span>
|
||||||
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
</li>
|
</li>
|
||||||
<!-- <li>
|
|
||||||
<span class="flex font-bold px-3 py-1 text-violet-900">Which counties have the...</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">
|
|
||||||
<icon-mdi-percent-outline class="mr-2"></icon-mdi-percent-outline> highest positivity rate?
|
|
||||||
</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> most cases by population?
|
|
||||||
</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-hospital-box-outline class="mr-2"></icon-mdi-hospital-box-outline> most hospitalizations by population?
|
|
||||||
</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-grave-stone class="mr-2"></icon-mdi-grave-stone> most deaths by population?
|
|
||||||
</router-link>
|
|
||||||
</li>
|
|
||||||
</ul>
|
|
||||||
</li> -->
|
|
||||||
<li>
|
<li>
|
||||||
<span class="flex font-bold px-3 py-1 text-violet-900">Who is most at-risk...</span>
|
<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">
|
<ul class="space-y-1 py-2 ml-3">
|
||||||
<li>
|
<li>
|
||||||
<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">
|
<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">
|
||||||
@@ -69,18 +50,36 @@
|
|||||||
</router-link>
|
</router-link>
|
||||||
</li>
|
</li>
|
||||||
<li class="flex items-center justify-between">
|
<li class="flex items-center justify-between">
|
||||||
<!-- <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"> -->
|
<!-- <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"> -->
|
||||||
<a href="#" class="text-base rounded-lg flex items-center p-2 mr-3 group opacity-50">
|
<a href="#" class="text-base rounded-lg flex items-center p-2 mr-3 group opacity-50">
|
||||||
<icon-mdi-gender-male-female class="mr-2"></icon-mdi-gender-male-female> by gender?
|
<icon-mdi-user class="mr-2"></icon-mdi-user> by ethnicity and sex?
|
||||||
</a>
|
</a>
|
||||||
<span class="rounded-lg bg-violet-700 text-white text-sm px-2 py-1 mr-3">Soon</span>
|
<span class="rounded-lg bg-violet-700 text-white text-sm px-2 py-1 mr-3">Planned</span>
|
||||||
</li>
|
</li>
|
||||||
<li class="flex items-center justify-between">
|
<li class="flex items-center justify-between">
|
||||||
<!-- <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"> -->
|
<!-- <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"> -->
|
||||||
<a href="#" class="text-base rounded-lg flex items-center p-2 mr-3 group opacity-50">
|
<a href="#" class="text-base rounded-lg flex items-center p-2 mr-3 group opacity-50">
|
||||||
<icon-mdi-web class="mr-2"></icon-mdi-web> by ethnicity?
|
<icon-mdi-syringe class="mr-2"></icon-mdi-syringe> by vaccination status?
|
||||||
</a>
|
</a>
|
||||||
<span class="rounded-lg bg-violet-700 text-white text-sm px-2 py-1 mr-3">Soon</span>
|
<span class="rounded-lg bg-violet-700 text-white text-sm px-2 py-1 mr-3">Planned</span>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</li>
|
||||||
|
|
||||||
|
<li>
|
||||||
|
<span class="flex font-bold px-3 py-1 text-violet-900">How many hospital...</span>
|
||||||
|
<ul class="space-y-1 py-2 ml-3">
|
||||||
|
<li class="flex items-center justify-between">
|
||||||
|
<a href="#" class="text-base rounded-lg flex items-center p-2 mr-3 group opacity-50">
|
||||||
|
<icon-healthicons-ventilator class="mr-2"></icon-healthicons-ventilator> resources are available?
|
||||||
|
</a>
|
||||||
|
<span class="rounded-lg bg-violet-700 text-white text-sm px-2 py-1 mr-3">Planned</span>
|
||||||
|
</li>
|
||||||
|
<li class="flex items-center justify-between">
|
||||||
|
<a href="#" class="text-base rounded-lg flex items-center p-2 mr-3 group opacity-50">
|
||||||
|
<icon-healthicons-hospitalized class="mr-2"></icon-healthicons-hospitalized> patients have COVID?
|
||||||
|
</a>
|
||||||
|
<span class="rounded-lg bg-violet-700 text-white text-sm px-2 py-1 mr-3">Planned</span>
|
||||||
</li>
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
</li>
|
</li>
|
||||||
|
|||||||
@@ -14,9 +14,9 @@
|
|||||||
<strong>Note:</strong> This is a citizen science project by <a href="https://thederf.com" class="link">Joshua Bemenderfer</a> and not affiliated with or endorsed in any way by the State of Georgia.
|
<strong>Note:</strong> This is a citizen science project by <a href="https://thederf.com" class="link">Joshua Bemenderfer</a> and not affiliated with or endorsed in any way by the State of Georgia.
|
||||||
</p>
|
</p>
|
||||||
<ul class="flex items-center mt-4 lg:mt-0">
|
<ul class="flex items-center mt-4 lg:mt-0">
|
||||||
<li class="text-center"><a href="/sources" class="font-normal text-violet-900 hover:bg-violet-100 rounded-lg p-2 px-3 block">Sources</a></li>
|
<li class="text-center"><a href="/#sources" class="font-normal text-violet-900 hover:bg-violet-100 rounded-lg p-2 px-3 block">Sources</a></li>
|
||||||
<li class="text-center"><a href="/about" class="font-normal text-violet-900 hover:bg-violet-100 rounded-lg p-2 px-3 block">About</a></li>
|
<li class="text-center"><a href="/#about" class="font-normal text-violet-900 hover:bg-violet-100 rounded-lg p-2 px-3 block">About</a></li>
|
||||||
<li class="text-center"><a href="mailto:josh@thederf.com" target="_blank" class="font-normal text-violet-900 hover:bg-violet-100 rounded-lg p-2 px-3 block">Contact</a></li>
|
<li class="text-center"><a href="/#contact" class="font-normal text-violet-900 hover:bg-violet-100 rounded-lg p-2 px-3 block">Contact</a></li>
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
</article>
|
</article>
|
||||||
|
|||||||
@@ -1,8 +1,143 @@
|
|||||||
---
|
---
|
||||||
title: Introduction
|
title: Home / About / Sources
|
||||||
---
|
---
|
||||||
|
|
||||||
<Card prose={true}>
|
<Card prose={true}>
|
||||||
# Introduction
|
<div id="about"></div>
|
||||||
|
# About
|
||||||
|
|
||||||
|
This site exists to provide a simplified, targeted view into information provided by the [Georgia Department of Health (DPH)](https://dph.georgia.gov/covid-19-daily-status-report) on the COVID-19 pandemic.
|
||||||
|
|
||||||
|
While the DPH provides an excellent range of datasets and visualizations, how to understand their utility and interpret them is left as an exercise to the reader.<br/>
|
||||||
|
Here I've attempted to provide explanations of the utility of certain statistics as well as provide some new visualizations not currently provided by the Department of Health dashboard.
|
||||||
|
|
||||||
|
### Disclaimer
|
||||||
|
The data provided on this site is based on information made available by the Georgia Department of Health at the time of reporting.
|
||||||
|
It may at times lack accuracy due to unreported, delayed, or improperly processed information.
|
||||||
|
Reporting time and quality for certain statistics may vary depending on the processes of individual organizations and jurisdictions.
|
||||||
|
|
||||||
|
This site is manually built from daily DPH reports. I will attempt to have new data incorporated by 5:00 PM on weekdays. Updates may be delayed due to personal circumstances.
|
||||||
|
|
||||||
|
**For case reports**, at-home rapid tests are not accounted for. Case numbers may be much higher than reported if [test positivity](/overall/testing) is unusually high.
|
||||||
|
|
||||||
|
### The following additional reports are planned
|
||||||
|
- **What is the overall trend in vaccinations?** - [Data available here](https://experience.arcgis.com/experience/3d8eea39f5c1443db1743a4cb8948a9c) as XLSX, pending processing.
|
||||||
|
- **Who is most at risk by ethnicity and sex?** - Data available in daily reports, pending processing.
|
||||||
|
- **Who is most at risk by vaccination status?** - Weekly reports on breakthrough cases and deaths [are available here](https://dph.georgia.gov/covid-19-breakthrough-reports), but challenging to process.
|
||||||
|
- **How many hospital resources are available?** - [Data available here](https://www.arcgis.com/apps/opsdashboard/index.html#/47c1cee4d02542bea35bc3324d6cf5e3) but requires querying ArcGIS directly.
|
||||||
|
- **How many hospital patients have COVID?** - [Data available here](https://www.arcgis.com/apps/opsdashboard/index.html#/e40c39564f724af7bfe8fd5d88deadb6) but requires querying ArcGIS directly.
|
||||||
|
|
||||||
|
**Please note:** *This is a citizen science project and is not affiliated with or endorsed in any way by the State of Georgia.*
|
||||||
|
</Card>
|
||||||
|
|
||||||
|
<Card class="bg-violet-900">
|
||||||
|
<div id="summary"></div>
|
||||||
|
<div class="prose prose-invert lg:prose-base prose-headings:font-light prose-a:text-violet-700 prose-a:font-normal mb-2">
|
||||||
|
# Summary of Current Overall Trends
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<p class="text-white prose prose-invert lg:prose-base mb-4">
|
||||||
|
For more details, see reporting pages for:
|
||||||
|
<div class="grid gap-2 grid-cols-2 sm:grid-cols-4 -mt-2">
|
||||||
|
<RouterLink to={{path: '/overall/testing'}} class="text-center font-normal no-underline text-white bg-violet-700 hover:bg-violet-500 rounded-lg p-2 px-3 block w-full">Testing</RouterLink>
|
||||||
|
<RouterLink to={{path: '/overall/testing'}} class="text-center font-normal no-underline text-white bg-violet-700 hover:bg-violet-500 rounded-lg p-2 px-3 block w-full">Cases</RouterLink>
|
||||||
|
<RouterLink to={{path: '/overall/testing'}} class="text-center font-normal no-underline text-white bg-violet-700 hover:bg-violet-500 rounded-lg p-2 px-3 block w-full">Hospitalizations</RouterLink>
|
||||||
|
<RouterLink to={{path: '/overall/testing'}} class="text-center font-normal no-underline text-white bg-violet-700 hover:bg-violet-500 rounded-lg p-2 px-3 block w-full">Deaths</RouterLink>
|
||||||
|
</div>
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<div class="grid gap-2 lg:gap-4 grid-cols-1 sm:grid-cols-2 lg:grid-cols-2 xl:grid-cols-3">
|
||||||
|
<MapCases client:load class="sm:col-span-2 xl:col-span-1 xl:row-span-3"/>
|
||||||
|
<ChipsSummary client:load/>
|
||||||
|
</div>
|
||||||
|
</Card>
|
||||||
|
|
||||||
|
|
||||||
|
<Card prose={true}>
|
||||||
|
<div id="sources"></div>
|
||||||
|
# Sources
|
||||||
|
|
||||||
|
### Data Sources
|
||||||
|
|
||||||
|
- Georgia Department of Health, COVID-19 Daily Status Report - [https://ga-covid19.ondemand.sas.com/docs/ga_covid_data.zip](https://ga-covid19.ondemand.sas.com/docs/ga_covid_data.zip)
|
||||||
|
- **Update Frequency** - The dataset above is updated each working day around 2:50 PM. I load it onto this site and rebuild shortly thereafter.
|
||||||
|
- **Additional Data** - Some reports on this site may derive additional time-based information by comparing totals from multiple daily reports.
|
||||||
|
- David Eldersveld, TopoJSON Collection, Georgia Counties TopoJSON - [https://github.com/deldersveld/topojson/blob/master/countries/us-states/GA-13-georgia-counties.json](https://github.com/deldersveld/topojson/blob/master/countries/us-states/GA-13-georgia-counties.json)
|
||||||
|
- Tweaked and used for rendering county maps.
|
||||||
|
|
||||||
|
### Site Source Code
|
||||||
|
|
||||||
|
The source code for this website can be found at [https://git.thederf.com/thederf/ga-covid.thederf.com](https://git.thederf.com/thederf/ga-covid.thederf.com).
|
||||||
|
|
||||||
|
Feel free to build it yourself, take a look around, validate the reports, and [suggest improvements](#contact).<br/>
|
||||||
|
*Note, the code is still very much in the "make it work" stage, and has much room for being simplified, abstracted, and cleaned up.*
|
||||||
|
|
||||||
|
#### Licensing
|
||||||
|
|
||||||
|
The source code and content of this site is provided under the [MIT license](https://opensource.org/licenses/MIT).<br/>
|
||||||
|
Datasets, libraries, and other assets used on this site are distributed or used according to the licenses of the original copyright holders to the best of my knowledge.
|
||||||
|
|
||||||
|
#### Stack
|
||||||
|
|
||||||
|
- [îles](https://iles-docs.netlify.app/) - Generates static pages from MDX files and Vue components, with optional component-level client-side hydration.
|
||||||
|
- Excellent for getting a site built with a modern tech stack and minimal extraneous config. Still in beta with some rough edges, but I greatly enjoyed working with it.
|
||||||
|
- [Vue 3](https://v3.vuejs.org/) - Used for layout and client-side components such as charts.
|
||||||
|
- [JSCharting](https://jscharting.com/) - Used for charts. Straightforward to get up and running with, but provides a vast array of data visualization options.
|
||||||
|
- [TailwindCSS](https://tailwindcss.com/) - Because people keep telling me to try it. I get the appeal. It made style tweaking incredibly fast and fluid, but it definitely results in messy markup.
|
||||||
|
|
||||||
|
#### Building & Running
|
||||||
|
|
||||||
|
1. Clone repository using [git](https://git-scm.com/)
|
||||||
|
```bash
|
||||||
|
$ git clone https://git.thederf.com/thederf/ga-covid.thederf.com.git
|
||||||
|
```
|
||||||
|
2. Install dependencies using [npm](https://www.npmjs.com/) or your preferred [node.js](https://nodejs.org) package manager of choice.
|
||||||
|
```bash
|
||||||
|
$ cd ga-covid.thederf.com/
|
||||||
|
$ npm install
|
||||||
|
```
|
||||||
|
3. (Optional) Rebuild the data json files.
|
||||||
|
```bash
|
||||||
|
npm run process:data
|
||||||
|
```
|
||||||
|
4. Run a local server for development
|
||||||
|
```bash
|
||||||
|
npm run dev
|
||||||
|
```
|
||||||
|
5. Build static site
|
||||||
|
```bash
|
||||||
|
npm run build
|
||||||
|
```
|
||||||
|
|
||||||
|
#### File Tree
|
||||||
|
|
||||||
|
```txt
|
||||||
|
data/ - Scripts for comverting the raw CSV source files into browser-friendly JSON, removing extraneous data points, and segmenting data into separate files.
|
||||||
|
- parser.js - Data parser entrypoint.
|
||||||
|
parser/... - Scripts for producing json files for specific reports.
|
||||||
|
raw/
|
||||||
|
- YYYY-MM-DD.zip - Raw Georgia DPH datasets from the link above, by date downloaded.
|
||||||
|
Including historical datasets allows for building time-series reports for data provided exclusively in daily counts.
|
||||||
|
...
|
||||||
|
public/ - Static assets.
|
||||||
|
maps/ - GeoJSON map files for rendering maps.
|
||||||
|
data/ - Generated JSON files prepared for browser loading by the parser above.
|
||||||
|
...
|
||||||
|
src/
|
||||||
|
assets/ - CSS & Image assets processed by Vite
|
||||||
|
components/ - Vue components for rendering client-interactive parts of the website.
|
||||||
|
charts/ - Components that handle chart & chip rendering.
|
||||||
|
layouts/ - Vue components for laying out the site. Primarily server-rendered.
|
||||||
|
pages/ - MDX files for site pages.
|
||||||
|
...
|
||||||
|
...
|
||||||
|
```
|
||||||
|
</Card>
|
||||||
|
|
||||||
|
<Card prose={true}>
|
||||||
|
<div id="contact"></div>
|
||||||
|
# Contact
|
||||||
|
|
||||||
|
If you have comments, concerns, requests, or recommendations, feel free to send me an email at
|
||||||
|
[ga-covid@thederf.com](mailto:ga-covid@thederf.com).
|
||||||
</Card>
|
</Card>
|
||||||
|
|||||||
@@ -14,7 +14,8 @@ title: What is the overall trend in testing?
|
|||||||
**For example:**<br/>
|
**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.
|
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.
|
**Test Positivity** can serve as a rough proxy for inferring how many cases go unreported.<br/>
|
||||||
|
If a high percentage of reported tests are coming back positive, odds are a significant number of cases are going unreported, especially when at-home tests are available.
|
||||||
</Card>
|
</Card>
|
||||||
|
|
||||||
<ParametersTesting client:load/>
|
<ParametersTesting client:load/>
|
||||||
|
|||||||
@@ -3,13 +3,13 @@ title: Who is most at risk by health condition?
|
|||||||
---
|
---
|
||||||
|
|
||||||
<Card class="col-span-1" prose={true}>
|
<Card class="col-span-1" prose={true}>
|
||||||
# Who is most at risk by condition?
|
# Who is most at risk by health condition?
|
||||||
|
|
||||||
## What is this report useful for?
|
## What is this report useful for?
|
||||||
|
|
||||||
This report provides charts indicating the number of cases and deaths for a number of tracked medical conditions.
|
This report provides charts indicating the number of cases and deaths for a number of tracked medical conditions.
|
||||||
|
|
||||||
You may find it useful for assessing the relative risk of COVID to yourself or others.
|
You may find it useful for assessing the relative risk of contracting COVID to yourself or others.
|
||||||
</Card>
|
</Card>
|
||||||
|
|
||||||
<div class="grid gap-2 lg:gap-4 grid-cols-1 xl:grid-cols-4">
|
<div class="grid gap-2 lg:gap-4 grid-cols-1 xl:grid-cols-4">
|
||||||
|
|||||||
Reference in New Issue
Block a user