mirror of
https://github.com/Lissy93/dashy.git
synced 2024-11-27 18:32:55 +03:00
✨ Adds Covid status widget
This commit is contained in:
parent
710b3ea7ad
commit
f5c11b3dc6
@ -23,6 +23,7 @@ Dashy has support for displaying dynamic content in the form of widgets. There a
|
||||
- [TFL Status](#tfl-status)
|
||||
- [Stock Price History](#stock-price-history)
|
||||
- [ETH Gas Prices](#eth-gas-prices)
|
||||
- [Covid-19 Status](#covid-19-status)
|
||||
- [Joke of the Day](#joke)
|
||||
- [XKCD Comics](#xkcd-comics)
|
||||
- [News Headlines](#news-headlines)
|
||||
@ -586,6 +587,53 @@ _No config options._
|
||||
|
||||
---
|
||||
|
||||
### Covid-19 Status
|
||||
|
||||
Keep track of the current COVID-19 status. Optionally also show cases by country, and a time-series chart. Uses live data from various sources, computed by [disease.sh](https://disease.sh/)
|
||||
|
||||
<p align="center"><img width="400" src="https://i.ibb.co/7XjbyRg/covid-19-status.png?" /></p>
|
||||
|
||||
##### Options
|
||||
|
||||
**Field** | **Type** | **Required** | **Description**
|
||||
--- | --- | --- | ---
|
||||
**`showChart`** | `boolean` | _Optional_ | Also display a time-series chart showing number of recent cases
|
||||
**`showCountries`** | `boolean` | _Optional_ |
|
||||
**`numDays`** | `number` | _Optional_ | Specify number of days worth of history to render on the chart
|
||||
**`countries`** | `string[]` | _Optional_ | An array of countries to display, specified by their [ISO-3 codes](https://www.iso.org/obp/ui). Leave blank to show all, sorted by most cases
|
||||
**`limit`** | `number` | _Optional_ | If showing all countries, set a limit for number of results to return. Defaults to `10`, no maximum
|
||||
|
||||
|
||||
##### Example
|
||||
|
||||
```yaml
|
||||
- type: covid-stats
|
||||
```
|
||||
|
||||
Or
|
||||
|
||||
```yaml
|
||||
- type: covid-stats
|
||||
options:
|
||||
showChart: true
|
||||
showCountries: true
|
||||
countries:
|
||||
- GBR
|
||||
- USA
|
||||
- IND
|
||||
- RUS
|
||||
```
|
||||
|
||||
##### Info
|
||||
- **CORS**: 🟢 Enabled
|
||||
- **Auth**: 🟢 Not Required
|
||||
- **Price**: 🟢 Free
|
||||
- **Host**: Managed Instance or Self-Hosted (see [disease-sh/api](https://github.com/disease-sh/api))
|
||||
- **Privacy**: ⚫ No Policy Available
|
||||
- **Conditions**: [Terms of Use](https://github.com/disease-sh/api/blob/master/TERMS.md)
|
||||
|
||||
---
|
||||
|
||||
### Joke
|
||||
|
||||
Renders a programming or generic joke. Data is fetched from the [JokesAPI](https://github.com/Sv443/JokeAPI) by @Sv443. All fields are optional.
|
||||
|
229
src/components/Widgets/CovidStats.vue
Normal file
229
src/components/Widgets/CovidStats.vue
Normal file
@ -0,0 +1,229 @@
|
||||
<template>
|
||||
<div class="covid-stats-wrapper">
|
||||
<div class="basic-stats" v-if="basicStats">
|
||||
<div class="active-cases stat-wrap">
|
||||
<span class="lbl">Active Cases</span>
|
||||
<span class="val">{{ basicStats.active | numberFormat }}</span>
|
||||
</div>
|
||||
<div class="more-stats">
|
||||
<div class="stat-wrap">
|
||||
<span class="lbl">Total Confirmed</span>
|
||||
<span class="val total">{{ basicStats.cases | numberFormat }}</span>
|
||||
</div>
|
||||
<div class="stat-wrap">
|
||||
<span class="lbl">Total Recovered</span>
|
||||
<span class="val recovered">{{ basicStats.deaths | numberFormat }}</span>
|
||||
</div>
|
||||
<div class="stat-wrap">
|
||||
<span class="lbl">Total Deaths</span>
|
||||
<span class="val deaths">{{ basicStats.recovered | numberFormat }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<!-- Chart -->
|
||||
<div class="case-history-chart" :id="chartId" v-if="showChart"></div>
|
||||
<!-- Country Data -->
|
||||
<div class="country-data" v-if="countryData">
|
||||
<div class="country-row" v-for="country in countryData" :key="country.name">
|
||||
<p class="name">
|
||||
<img :src="country.flag" alt="Flag" class="flag" />
|
||||
{{ country.name }}
|
||||
</p>
|
||||
<div class="country-case-wrap">
|
||||
<div class="stat-wrap">
|
||||
<span class="lbl">Confirmed</span>
|
||||
<span class="val total">{{ country.cases | showInK }}</span>
|
||||
</div>
|
||||
<div class="stat-wrap">
|
||||
<span class="lbl">Recovered</span>
|
||||
<span class="val recovered">{{ country.recovered | showInK }}</span>
|
||||
</div>
|
||||
<div class="stat-wrap">
|
||||
<span class="lbl">Deaths</span>
|
||||
<span class="val deaths">{{ country.deaths | showInK }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import WidgetMixin from '@/mixins/WidgetMixin';
|
||||
import ChartingMixin from '@/mixins/ChartingMixin';
|
||||
import { putCommasInBigNum, showNumAsThousand, timestampToDate } from '@/utils/MiscHelpers';
|
||||
import { widgetApiEndpoints } from '@/utils/defaults';
|
||||
|
||||
export default {
|
||||
mixins: [WidgetMixin, ChartingMixin],
|
||||
computed: {
|
||||
showChart() {
|
||||
return this.options.showChart || false;
|
||||
},
|
||||
showCountries() {
|
||||
if (this.options.countries) return true;
|
||||
return this.options.showCountries;
|
||||
},
|
||||
numDays() {
|
||||
return this.options.numDays || 120;
|
||||
},
|
||||
countries() {
|
||||
return this.options.countries;
|
||||
},
|
||||
limit() {
|
||||
return this.options.limit || 15;
|
||||
},
|
||||
basicStatsEndpoint() {
|
||||
return `${widgetApiEndpoints.covidStats}/all`;
|
||||
},
|
||||
timeSeriesEndpoint() {
|
||||
return `${widgetApiEndpoints.covidStats}/historical/all?lastdays=${this.numDays}`;
|
||||
},
|
||||
countryInfoEndpoint() {
|
||||
return 'https://covidapi.yubrajpoudel.com.np/stat';
|
||||
},
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
basicStats: null,
|
||||
countryData: null,
|
||||
};
|
||||
},
|
||||
filters: {
|
||||
numberFormat(caseNumber) {
|
||||
return putCommasInBigNum(caseNumber);
|
||||
},
|
||||
showInK(caseNumber) {
|
||||
return showNumAsThousand(caseNumber);
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
fetchData() {
|
||||
this.makeRequest(this.basicStatsEndpoint).then(this.processBasicStats);
|
||||
if (this.showChart) {
|
||||
this.makeRequest(this.timeSeriesEndpoint).then(this.processTimeSeries);
|
||||
}
|
||||
if (this.showCountries) {
|
||||
this.makeRequest(this.countryInfoEndpoint).then(this.processCountryInfo);
|
||||
}
|
||||
},
|
||||
processBasicStats(data) {
|
||||
this.basicStats = data;
|
||||
},
|
||||
processCountryInfo(data) {
|
||||
const countryData = [];
|
||||
data.forEach((country) => {
|
||||
const iso = country.countryInfo.iso3;
|
||||
if (!this.countries || this.countries.includes(iso)) {
|
||||
countryData.push({
|
||||
name: country.country,
|
||||
flag: country.countryInfo.flag,
|
||||
cases: country.cases,
|
||||
deaths: country.deaths,
|
||||
recovered: country.recovered,
|
||||
});
|
||||
}
|
||||
});
|
||||
this.countryData = countryData.slice(0, this.limit);
|
||||
},
|
||||
processTimeSeries(data) {
|
||||
const timeLabels = Object.keys(data.cases);
|
||||
const totalCases = [];
|
||||
const totalDeaths = [];
|
||||
const totalRecovered = [];
|
||||
timeLabels.forEach((date) => {
|
||||
totalCases.push(data.cases[date]);
|
||||
totalDeaths.push(data.deaths[date]);
|
||||
totalRecovered.push(data.recovered[date]);
|
||||
});
|
||||
const chartData = {
|
||||
labels: timeLabels,
|
||||
datasets: [
|
||||
{ name: 'Cases', type: 'bar', values: totalCases },
|
||||
{ name: 'Recovered', type: 'bar', values: totalRecovered },
|
||||
{ name: 'Deaths', type: 'bar', values: totalDeaths },
|
||||
],
|
||||
};
|
||||
return new this.Chart(`#${this.chartId}`, {
|
||||
title: 'Cases, Recoveries and Deaths',
|
||||
data: chartData,
|
||||
type: 'axis-mixed',
|
||||
height: this.chartHeight,
|
||||
colors: ['#f6f000', '#20e253', '#f80363'],
|
||||
truncateLegends: true,
|
||||
lineOptions: {
|
||||
hideDots: 1,
|
||||
},
|
||||
axisOptions: {
|
||||
xIsSeries: true,
|
||||
xAxisMode: 'tick',
|
||||
},
|
||||
tooltipOptions: {
|
||||
formatTooltipY: d => putCommasInBigNum(d),
|
||||
formatTooltipX: d => timestampToDate(d),
|
||||
},
|
||||
});
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.covid-stats-wrapper {
|
||||
.basic-stats {
|
||||
padding: 0.5rem 0;
|
||||
margin: 0.5rem 0;
|
||||
background: var(--widget-accent-color);
|
||||
border-radius: var(--curve-factor);
|
||||
}
|
||||
.country-row {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
p.name {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
margin: 0.5rem 0;
|
||||
color: var(--widget-text-color);
|
||||
img.flag {
|
||||
width: 2.5rem;
|
||||
height: 1.5rem;
|
||||
margin-right: 0.5rem;
|
||||
border-radius: var(--curve-factor);
|
||||
}
|
||||
}
|
||||
.country-case-wrap {
|
||||
min-width: 60%;
|
||||
}
|
||||
&:not(:last-child) { border-bottom: 1px dashed var(--widget-text-color); }
|
||||
}
|
||||
.stat-wrap {
|
||||
color: var(--widget-text-color);
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
width: 33%;
|
||||
margin: 0.25rem auto;
|
||||
text-align: center;
|
||||
cursor: default;
|
||||
span.lbl {
|
||||
font-size: 0.8rem;
|
||||
opacity: var(--dimming-factor);
|
||||
}
|
||||
span.val {
|
||||
font-weight: bold;
|
||||
margin: 0.1rem 0;
|
||||
font-family: var(--font-monospace);
|
||||
&.total { color: var(--warning); }
|
||||
&.recovered { color: var(--success); }
|
||||
&.deaths { color: var(--danger); }
|
||||
}
|
||||
&.active-cases {
|
||||
span.lbl { font-size: 1.1rem; }
|
||||
span.val { font-size: 1.3rem; }
|
||||
}
|
||||
}
|
||||
.more-stats, .country-case-wrap {
|
||||
display: flex;
|
||||
justify-content: space-around;
|
||||
}
|
||||
}
|
||||
</style>
|
@ -60,6 +60,13 @@
|
||||
@error="handleError"
|
||||
:ref="widgetRef"
|
||||
/>
|
||||
<CovidStats
|
||||
v-else-if="widgetType === 'covid-stats'"
|
||||
:options="widgetOptions"
|
||||
@loading="setLoaderState"
|
||||
@error="handleError"
|
||||
:ref="widgetRef"
|
||||
/>
|
||||
<EmbedWidget
|
||||
v-else-if="widgetType === 'embed'"
|
||||
:options="widgetOptions"
|
||||
@ -282,6 +289,7 @@ export default {
|
||||
Apod: () => import('@/components/Widgets/Apod.vue'),
|
||||
Clock: () => import('@/components/Widgets/Clock.vue'),
|
||||
CodeStats: () => import('@/components/Widgets/CodeStats.vue'),
|
||||
CovidStats: () => import('@/components/Widgets/CovidStats.vue'),
|
||||
CryptoPriceChart: () => import('@/components/Widgets/CryptoPriceChart.vue'),
|
||||
CryptoWatchList: () => import('@/components/Widgets/CryptoWatchList.vue'),
|
||||
CveVulnerabilities: () => import('@/components/Widgets/CveVulnerabilities.vue'),
|
||||
|
@ -210,6 +210,7 @@ module.exports = {
|
||||
widgetApiEndpoints: {
|
||||
astronomyPictureOfTheDay: 'https://apodapi.herokuapp.com/api',
|
||||
codeStats: 'https://codestats.net/',
|
||||
covidStats: 'https://disease.sh/v3/covid-19',
|
||||
cryptoPrices: 'https://api.coingecko.com/api/v3/coins/',
|
||||
cryptoWatchList: 'https://api.coingecko.com/api/v3/coins/markets/',
|
||||
cveVulnerabilities: 'https://www.cvedetails.com/json-feed.php',
|
||||
|
Loading…
Reference in New Issue
Block a user