Initial commit.

This commit is contained in:
Joshua Bemenderfer
2021-12-28 21:52:58 -05:00
commit bac61fe227
85 changed files with 1317616 additions and 0 deletions

3
src/app.ts Normal file
View File

@@ -0,0 +1,3 @@
import { defineApp } from 'iles'
export default defineApp({})

21
src/assets/base.css Normal file
View File

@@ -0,0 +1,21 @@
@tailwind base;
@tailwind components;
@tailwind utilities;
@tailwind typography;
a.link {
@apply underline text-violet-700;
}
.input {
@apply inline-block h-11 m-2 p-2 px-3 rounded-lg bg-white border border-indigo-100 outline-none
transition-shadow shadow shadow-indigo-200 hover:border-indigo-400 focus:border-indigo-600 active:border-indigo-600;
}
select {
@apply input;
}
input {
@apply input;
}

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 52 KiB

0
src/components/Data.vue Normal file
View File

View File

@@ -0,0 +1,85 @@
<template>
<label>
Counties
<select v-model="selectedCounty">
<option v-for="county of counties" :key="county" :value="county">{{county}}</option>
</select>
</label>
<label>
Statistic
<select v-model="selectedStat">
<option v-for="(func, stat) of statistics" :key="stat" :value="stat">{{stat}}</option>
</select>
</label>
<br>
<label>
Start
<input type="date" v-model="selectedStartDate"/>
</label>
<label>
End
<input type="date" v-model="selectedEndDate"/>
</label>
<br>
<JSCharting v-if="data.length" :options="chartOptions"></JSCharting>
</template>
<script setup lang="ts">
import { ref, computed, onMounted } from 'vue'
import Papa from 'papaparse'
let data = ref([])
let selectedCounty = ref('Georgia')
let selectedStat = ref('Positive PCR Tests')
let selectedStartDate = ref('2021-01-01')
let selectedEndDate = ref('2021-12-31')
const counties = computed(() => {
return Array.from(new Set(data.value.map(r => r.county)))
})
const statistics = {
'PCR Tests Performed': r => r['ALL PCR tests performed'],
'Positive PCR Tests': r => r['All PCR positive tests'],
'PCR+Antigen Tests Performed': r => parseInt(r['ALL PCR tests performed']) + parseInt(r['Antigen Tests Performed']),
'Antigen Tests Performed': r => r['Antigen Tests Performed'],
'Positive Antigen Tests': r => r['Antigen Positive Tests'],
'Positive PCR & Antigen Tests': r => parseInt(r['All PCR positive tests']) + parseInt(r['Antigen Positive Tests']),
'7-day Percent Positive': r => r['7 day percent positive'],
'14-day Percent Positive': r => r['14 day percent positive'],
}
const colors = ['#f5f3ff','#ede9fe','#ddd6fe','#c4b5fd','#a78bfa','#8b5cf6','#7c3aed','#6d28d9','#5b21b6','#4c1d95']
const chartOptions = computed(() => {
return {
type: 'calendar solid',
palette: { colors },
data: data.value
.filter(r => {
return r.county == selectedCounty.value
&& r.report_date >= selectedStartDate.value
&& r.report_date <= selectedEndDate.value
})
.map(r => ([
`${r.report_date} 12:00:00`,
+statistics[selectedStat.value](r)
]))
}
})
async function getData() {
const csv = await fetch('/data/2021-12-28/pcr_antigen.csv').then(res => res.text())
return Papa.parse(csv, {
header: true
}).data
}
onMounted(async () => {
data.value = await getData()
})
</script>

View File

@@ -0,0 +1,76 @@
<template>
<div></div>
</template>
<script>
import { Chart } from 'jscharting';
export default {
name: 'JSCharting',
props: {
options: { type: Object, required: true },
mutable: { type: Boolean, default: true },
ignoreStateUpdate: { type: Boolean, default: false }
},
data() {
return {
instance: undefined
};
},
mounted: function() {
this.createChart();
},
beforeUnmount: function() {
this.destroyChart();
},
watch: {
options: {
deep: true,
handler(oldValue, newValue) {
// Skip update if chart exists and ignoreStateUpdate is true
if (this.instance && this.ignoreStateUpdate) return;
if (oldValue !== newValue) {
this.destroyChart()
this.createChart()
} else {
this.createChart();
}
}
}
},
methods: {
createChart: function() {
const options = this.options || {};
// If the instance does not exist yet, create one
if (!this.instance) {
this.renderChart(options);
return;
}
if (this.mutable && !this.instance.dirty) {
// If the mutable is true and the instance is not dirty, update the existing instance
this.instance.options(options);
} else {
// Create a new instance with the new values
this.renderChart(options);
}
},
renderChart(options) {
this.destroyChart();
this.instance = new Chart(
options.targetElement || this.$el,
options,
chart => this.$emit('rendered', chart)
);
},
destroyChart: function() {
if (this.instance) {
this.instance.destroy();
this.instance = undefined;
}
}
}
};
</script>

View File

@@ -0,0 +1,47 @@
<template>
<div></div>
</template>
<script>
import * as JSC from 'jscharting';
export default {
name: 'JSCGrid',
props: {
options: { type: Object, required: true },
mutable: { type: Boolean, default: true }
},
mounted: function() {
this.renderGrid();
},
beforeUnmount: function() {
this.destroyGrid();
},
watch: {
options: function() {
this.renderGrid();
}
},
methods: {
destroyGrid: function() {
if (this.instance) {
this.instance.destroy();
this.instance = undefined;
}
},
renderGrid: function() {
const options = this.options;
const updateExisting = this.instance && this.mutable !== false;
if (updateExisting) {
this.instance.options(this.options);
} else {
this.destroyGrid();
const containerElement = this.$el;
JSC.Grid(containerElement, options).then(grid => {
this.instance = grid;
this.$emit('rendered', grid);
});
}
}
}
};
</script>

View File

@@ -0,0 +1,35 @@
<template>
<div></div>
</template>
<script>
import * as JSC from 'jscharting';
export default {
name: 'JSCLabel',
props: {
options: { type: String, default: '' }
},
mounted: function() {
this.renderLabel();
},
beforeUnmount: function() {
this.destroyLabel();
},
watch: {
options: function() {
this.renderLabel();
}
},
methods: {
destroyLabel: function() {
const containerElement = this.$el;
containerElement.innerHTML = '';
},
renderLabel: function() {
this.destroyLabel();
const containerElement = this.$el;
JSC.label(containerElement, this.options);
}
}
};
</script>

View File

@@ -0,0 +1,114 @@
<template>
<ul class="space-y-2 pb-2">
<li>
<a href="/" class="text-base text-violet-900 font-bold flex flex-col rounded-lg p-2 px-3 hover:bg-violet-100 group">
Introduction
</a>
</li>
<li>
<span class="flex font-bold px-3 py-1 text-violet-900">What is the statewide trend in...</span>
<ul class="space-y-1 py-2 ml-3">
<li>
<a href="/cases" class="text-base text-gray-900 rounded-lg flex items-center p-2 hover:bg-violet-100 group">
<icon-mdi-test-tube class="mr-2"></icon-mdi-test-tube> testing?
</a>
</li>
<li>
<a href="/cases" class="text-base text-gray-900 rounded-lg flex items-center p-2 hover:bg-violet-100 group">
<icon-mdi-percent-outline class="mr-2"></icon-mdi-percent-outline> test positivity?
</a>
</li>
<li>
<a href="/cases" class="text-base text-gray-900 rounded-lg flex items-center p-2 hover:bg-violet-100 group">
<icon-mdi-virus-outline class="mr-2"></icon-mdi-virus-outline> cases?
</a>
</li>
<li>
<a href="/cases" class="text-base text-gray-900 rounded-lg flex items-center p-2 hover:bg-violet-100 group">
<icon-mdi-hospital-box-outline class="mr-2"></icon-mdi-hospital-box-outline> hospitalizations?
</a>
</li>
<li>
<a href="/cases" class="text-base text-gray-900 rounded-lg flex items-center p-2 hover:bg-violet-100 group">
<icon-mdi-grave-stone class="mr-2"></icon-mdi-grave-stone> deaths?
</a>
</li>
</ul>
</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>
<a href="/cases" class="text-base text-gray-900 rounded-lg flex items-center p-2 hover:bg-violet-100 group">
<icon-mdi-percent-outline class="mr-2"></icon-mdi-percent-outline> highest positivity rate?
</a>
</li>
<li>
<a href="/cases" class="text-base text-gray-900 rounded-lg flex items-center p-2 hover:bg-violet-100 group">
<icon-mdi-virus-outline class="mr-2"></icon-mdi-virus-outline> most cases by population?
</a>
</li>
<li>
<a href="/cases" class="text-base text-gray-900 rounded-lg flex items-center p-2 hover:bg-violet-100 group">
<icon-mdi-hospital-box-outline class="mr-2"></icon-mdi-hospital-box-outline> most hospitalizations by population?
</a>
</li>
<li>
<a href="/cases" class="text-base text-gray-900 rounded-lg flex items-center p-2 hover:bg-violet-100 group">
<icon-mdi-grave-stone class="mr-2"></icon-mdi-grave-stone> most deaths by population?
</a>
</li>
</ul>
</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>
<a href="/cases" class="text-base text-gray-900 rounded-lg flex items-center p-2 hover:bg-violet-100 group">
<icon-mdi-human-cane class="mr-2"></icon-mdi-human-cane> by age?
</a>
</li>
<li>
<a href="/cases" class="text-base text-gray-900 rounded-lg flex items-center p-2 hover:bg-violet-100 group">
<icon-mdi-gender-male-female class="mr-2"></icon-mdi-gender-male-female> by gender?
</a>
</li>
<li>
<a href="/cases" class="text-base text-gray-900 rounded-lg flex items-center p-2 hover:bg-violet-100 group">
<icon-mdi-web class="mr-2"></icon-mdi-web> by ethnicity?
</a>
</li>
<li>
<a href="/cases" class="text-base text-gray-900 rounded-lg flex items-center p-2 hover:bg-violet-100 group">
<icon-healthicons-clinical-f-outline class="mr-2"></icon-healthicons-clinical-f-outline> by health condition?
</a>
</li>
</ul>
</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>
<a href="/cases" class="text-base text-gray-900 rounded-lg flex items-center p-2 hover:bg-violet-100 group">
<icon-mdi-human-cane class="mr-2"></icon-mdi-human-cane> by age?
</a>
</li>
<li>
<a href="/cases" class="text-base text-gray-900 rounded-lg flex items-center p-2 hover:bg-violet-100 group">
<icon-mdi-gender-male-female class="mr-2"></icon-mdi-gender-male-female> by gender?
</a>
</li>
<li>
<a href="/cases" class="text-base text-gray-900 rounded-lg flex items-center p-2 hover:bg-violet-100 group">
<icon-mdi-web class="mr-2"></icon-mdi-web> by ethnicity?
</a>
</li>
<li>
<a href="/cases" class="text-base text-gray-900 rounded-lg flex items-center p-2 hover:bg-violet-100 group">
<icon-healthicons-clinical-f-outline class="mr-2"></icon-healthicons-clinical-f-outline> by health condition?
</a>
</li>
</ul>
</li>
</ul>
</template>

73
src/layouts/default.vue Normal file
View File

@@ -0,0 +1,73 @@
<template>
<!-- This is an example component -->
<div class="flex min-h-screen">
<nav class="bg-white fixed w-full z-30 shadow-md lg:shadow-none">
<div class="px-3 py-3 lg:px-5 lg:pl-3 h-16">
<div class="flex items-center h-full">
<a class="flex h-full pr-5 font-bold text-violet-900 items-center" href="/">
<img src="@/assets/ga-state-flag.svg" class="block h-full object-contain py-2 mr-2"/>
Georgia COVID-19 Charts
</a>
<div class="flex-1"></div>
<button @click="toggleSidebar()" aria-expanded="true" aria-controls="sidebar" class="lg:hidden ml-2 text-gray-600 hover:text-gray-900 cursor-pointer p-2 hover:bg-gray-100 focus:bg-gray-100 focus:ring-2 focus:ring-gray-100 rounded">
<svg :class="['w-6 h-6', isOpen ? 'hidden' : '']" class="w-6 h-6" fill="currentColor" viewBox="0 0 20 20" xmlns="http://www.w3.org/2000/svg">
<path fill-rule="evenodd" d="M3 5a1 1 0 011-1h12a1 1 0 110 2H4a1 1 0 01-1-1zM3 10a1 1 0 011-1h6a1 1 0 110 2H4a1 1 0 01-1-1zM3 15a1 1 0 011-1h12a1 1 0 110 2H4a1 1 0 01-1-1z" clip-rule="evenodd"></path>
</svg>
<svg :class="['w-6 h-6', isOpen ? '' : 'hidden']" fill="currentColor" viewBox="0 0 20 20" xmlns="http://www.w3.org/2000/svg">
<path fill-rule="evenodd" d="M4.293 4.293a1 1 0 011.414 0L10 8.586l4.293-4.293a1 1 0 111.414 1.414L11.414 10l4.293 4.293a1 1 0 01-1.414 1.414L10 11.414l-4.293 4.293a1 1 0 01-1.414-1.414L8.586 10 4.293 5.707a1 1 0 010-1.414z" clip-rule="evenodd"></path>
</svg>
</button>
<div class="flex-1"></div>
<em class="font-light hidden lg:block">
This is a citizen science project and not affiliated with or endorsed in any way by the State of Georgia.<br/>
</em>
</div>
</div>
</nav>
<aside ref="sidebar" :class="[
'fixed z-20 min-h-full max-h-screen lg:max-h-full pt-16 left-0 flex lg:flex flex-shrink-0 flex-col w-full lg:w-96 transition-transform origin-top duration-75 lg:relative lg:scale-y-100',
isOpen ? 'scale-y-100' : 'scale-y-0'
]" aria-label="Sidebar">
<div class="flex-1 flex flex-col overflow-y-auto px-3 bg-white space-y-4">
<Navigation></Navigation>
</div>
</aside>
<main class="w-full min-h-full relative overflow-y-auto mt-16 shadow-inner bg-violet-100 rounded-t-xl">
<div class="p-2 lg:p-6">
<div class="w-full space-y-2 lg:space-y-4 flex flex-col h-full">
<div class="bg-white shadow rounded-lg p-4 lg:p-8">
<article class="max-w-none prose lg:prose-base prose-headings:font-light prose-headings:text-indigo-900">
<slot></slot>
</article>
</div>
<div class="flex-1"></div>
<div class="bg-white font-light shadow rounded-lg p-4 lg:p-8 flex flex-wrap items-center justify-between">
<p>
<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>
<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="/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="/contact" class="font-normal text-violet-900 hover:bg-violet-100 rounded-lg p-2 px-3 block">Contact</a></li>
</ul>
</div>
</div>
</div>
</main>
</div>
</template>
<script setup lang="ts">
import Navigation from './components/Navigation.vue'
import { ref } from 'vue'
const isOpen = ref(false)
function toggleSidebar() {
isOpen.value = !isOpen.value
}
</script>
<style>
@import '~/assets/base.css';
</style>

8
src/pages/index.mdx Normal file
View File

@@ -0,0 +1,8 @@
---
title: Introduction
---
# Introduction
Example stats/day calendar chart
<CasesPerDay></CasesPerDay>

4
src/site.ts Normal file
View File

@@ -0,0 +1,4 @@
export default {
title: 'COVID-19 Statistics for Georgia',
description: 'Sourced from the Georgia Department of Public Health'
}