├── .gitattributes
├── static
├── images
│ └── favicon.png
├── js
│ ├── fetchApi.js
│ ├── covidApi.js
│ └── app.js
└── css
│ ├── grid.css
│ └── app.css
├── README.md
└── index.html
/.gitattributes:
--------------------------------------------------------------------------------
1 | # Auto detect text files and perform LF normalization
2 | * text=auto
3 |
--------------------------------------------------------------------------------
/static/images/favicon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/trananhtuat/covid-tracker/HEAD/static/images/favicon.png
--------------------------------------------------------------------------------
/static/js/fetchApi.js:
--------------------------------------------------------------------------------
1 | fetchRequest = async (url) => {
2 | const response = await fetch(url)
3 | return response.json()
4 | }
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Coronavirus (COVID-19) Tracker Dashboard
2 |
3 | Coronavirus (COVID-19) Tracker Dashboard with Data from API - HTML CSS JavaScript
4 |
5 | ## Video coding
6 |
7 | https://youtu.be/EoK2nIS1Zi4
8 |
9 | ## Preview
10 |
11 |  Tracker Dashboard")
12 |
--------------------------------------------------------------------------------
/static/js/covidApi.js:
--------------------------------------------------------------------------------
1 | const covidApi = {
2 | getSummary: async () => {
3 | return await fetchRequest(covidApiEndPoints.summary())
4 | },
5 | getWorldAllTimeCases: async () => {
6 | return await fetchRequest(covidApiEndPoints.worldAllTimeCases())
7 | },
8 | getCountryAllTimeCases: async (country, status) => {
9 | return await fetchRequest(covidApiEndPoints.countryAllTimeCases(country, status))
10 | },
11 | getWorldDaysCases: async () => {
12 | return await fetchRequest(covidApiEndPoints.worldDaysCases())
13 | },
14 | getCountryDaysCases: async (country, status) => {
15 | return await fetchRequest(covidApiEndPoints.countryDaysCases(country, status))
16 | }
17 | }
18 |
19 | const covid_api_base = 'https://api.covid19api.com/'
20 |
21 | const covidApiEndPoints = {
22 | summary: () => {
23 | return getApiPath('summary')
24 | },
25 | worldAllTimeCases: () => {
26 | return getApiPath('world')
27 | },
28 | countryAllTimeCases: (country, status) => {
29 | let end_point = `dayone/country/${country}/status/${status}`
30 | return getApiPath(end_point)
31 | },
32 | countryDaysCases: (country, status) => {
33 | let date = getDaysRange(30)
34 |
35 | let end_point = `country/${country}/status/${status}?from=${date.start_date}&to=${date.end_date}`
36 |
37 | return getApiPath(end_point)
38 | },
39 | worldDaysCases: () => {
40 | let date = getDaysRange(30)
41 |
42 | let end_point = `world?from=${date.start_date}&to=${date.end_date}`
43 |
44 | return getApiPath(end_point)
45 | }
46 | }
47 |
48 | // get the date at days before today
49 | getDaysRange = (days) => {
50 | let d = new Date()
51 |
52 | let from_d = new Date(d.getTime() - (days * 24 * 60 * 60 * 1000))
53 |
54 | let to_date = `${d.getFullYear()}-${d.getMonth() + 1}-${d.getDate()}`
55 |
56 | let from_date = `${from_d.getFullYear()}-${from_d.getMonth() + 1}-${from_d.getDate()}`
57 |
58 | return {
59 | start_date: from_date,
60 | end_date: to_date
61 | }
62 | }
63 |
64 | getApiPath = (end_point) => {
65 | return covid_api_base + end_point
66 | }
--------------------------------------------------------------------------------
/static/css/grid.css:
--------------------------------------------------------------------------------
1 | .row {
2 | display: flex;
3 | flex-wrap: wrap;
4 | margin-left: -15px;
5 | margin-right: -15px;
6 | }
7 |
8 | [class*="col-"] {
9 | padding: 0 15px;
10 | }
11 |
12 | .col-1 {
13 | width: 8.33%;
14 | }
15 |
16 | .col-2 {
17 | width: 16.66%;
18 | }
19 |
20 | .col-3 {
21 | width: 25%;
22 | }
23 |
24 | .col-4 {
25 | width: 33.33%;
26 | }
27 |
28 | .col-5 {
29 | width: 41.66%;
30 | }
31 |
32 | .col-6 {
33 | width: 50%;
34 | }
35 |
36 | .col-7 {
37 | width: 58.33%;
38 | }
39 |
40 | .col-8 {
41 | width: 66.66%;
42 | }
43 |
44 | .col-9 {
45 | width: 75%;
46 | }
47 |
48 | .col-10 {
49 | width: 83.33%;
50 | }
51 |
52 | .col-11 {
53 | width: 91.66%;
54 | }
55 |
56 | .col-12 {
57 | width: 100%;
58 | }
59 |
60 | /* medium screen */
61 | @media only screen and (max-width: 1280px) {
62 | .col-md-1 {
63 | width: 8.33%;
64 | }
65 |
66 | .col-md-2 {
67 | width: 16.66%;
68 | }
69 |
70 | .col-md-3 {
71 | width: 25%;
72 | }
73 |
74 | .col-md-4 {
75 | width: 33.33%;
76 | }
77 |
78 | .col-md-5 {
79 | width: 41.66%;
80 | }
81 |
82 | .col-md-6 {
83 | width: 50%;
84 | }
85 |
86 | .col-md-7 {
87 | width: 58.33%;
88 | }
89 |
90 | .col-md-8 {
91 | width: 66.66%;
92 | }
93 |
94 | .col-md-9 {
95 | width: 75%;
96 | }
97 |
98 | .col-md-10 {
99 | width: 83.33%;
100 | }
101 |
102 | .col-md-11 {
103 | width: 91.66%;
104 | }
105 |
106 | .col-md-12 {
107 | width: 100%;
108 | }
109 | }
110 |
111 | /* small screen */
112 | @media only screen and (max-width: 850px) {
113 | .col-sm-1 {
114 | width: 8.33%;
115 | }
116 |
117 | .col-sm-2 {
118 | width: 16.66%;
119 | }
120 |
121 | .col-sm-3 {
122 | width: 25%;
123 | }
124 |
125 | .col-sm-4 {
126 | width: 33.33%;
127 | }
128 |
129 | .col-sm-5 {
130 | width: 41.66%;
131 | }
132 |
133 | .col-sm-6 {
134 | width: 50%;
135 | }
136 |
137 | .col-sm-7 {
138 | width: 58.33%;
139 | }
140 |
141 | .col-sm-8 {
142 | width: 66.66%;
143 | }
144 |
145 | .col-sm-9 {
146 | width: 75%;
147 | }
148 |
149 | .col-sm-10 {
150 | width: 83.33%;
151 | }
152 |
153 | .col-sm-11 {
154 | width: 91.66%;
155 | }
156 |
157 | .col-sm-12 {
158 | width: 100%;
159 | }
160 | }
161 |
--------------------------------------------------------------------------------
/static/css/app.css:
--------------------------------------------------------------------------------
1 | :root {
2 | --bg-body: #fafbfd;
3 | --bg-content: #ffffff;
4 | --bg-hover: #f4f4f4;
5 |
6 | --color-txt: #172b4d;
7 |
8 | --nav-height: 70px;
9 |
10 | --shadow: 0 0 30px 0 rgb(82 63 105 / 5%);
11 | }
12 |
13 | .dark {
14 | --bg-body: #151515;
15 | --bg-content: #242526;
16 | --bg-hover: #151f28;
17 |
18 | --color-txt: #dcdcdc;
19 | }
20 |
21 | * {
22 | padding: 0;
23 | margin: 0;
24 | box-sizing: border-box;
25 | }
26 |
27 | a {
28 | text-decoration: none;
29 | }
30 |
31 | body {
32 | font-family: "Cairo", sans-serif;
33 | position: relative;
34 | background-color: var(--bg-body);
35 | color: var(--color-txt);
36 | padding-top: calc(var(--nav-height) + 30px);
37 | font-size: 16px;
38 | }
39 |
40 | .container {
41 | max-width: 1600px;
42 | margin: auto;
43 | }
44 |
45 | .content {
46 | padding: 15px;
47 | }
48 |
49 | /* top nav */
50 | .nav-wrapper {
51 | background-color: var(--bg-content);
52 | box-shadow: var(--shadow);
53 | position: fixed;
54 | top: 0;
55 | width: 100%;
56 | padding: 0 30px;
57 | z-index: 99;
58 | }
59 |
60 | .nav {
61 | height: var(--nav-height);
62 | display: flex;
63 | align-items: center;
64 | justify-content: space-between;
65 | }
66 |
67 | .logo {
68 | color: var(--color-txt);
69 | font-size: 2rem;
70 | font-weight: 900;
71 | }
72 |
73 | .logo i {
74 | color: red;
75 | }
76 |
77 | .darkmode-switch {
78 | --width: 60px;
79 | --height: 20px;
80 | width: var(--width);
81 | height: var(--height);
82 | background-color: lightslategray;
83 | border-radius: 10px;
84 | position: relative;
85 | cursor: pointer;
86 | }
87 |
88 | .darkmode-switch span {
89 | display: inline-grid;
90 | place-items: center;
91 | height: calc(var(--height) * 2);
92 | width: calc(var(--height) * 2);
93 | border-radius: 50%;
94 | background-color: var(--color-txt);
95 | color: var(--bg-content);
96 | font-size: 2rem;
97 | position: absolute;
98 | left: 0;
99 | top: calc(-1 * var(--height) / 2);
100 | transition: left 0.3s ease-in-out;
101 | }
102 |
103 | .darkmode-switch.dark span {
104 | left: calc(var(--width) - var(--height) * 2);
105 | background-color: var(--bg-body);
106 | color: var(--color-txt);
107 | }
108 |
109 | .darkmode-switch span .bxs-moon {
110 | display: none;
111 | }
112 |
113 | .darkmode-switch.dark span .bxs-moon {
114 | display: inline-block;
115 | }
116 |
117 | .darkmode-switch.dark span .bxs-sun {
118 | display: none;
119 | }
120 | /* end top nav */
121 |
122 | /* box */
123 | .box {
124 | width: 100%;
125 | border-radius: 10px;
126 | background-color: var(--bg-content);
127 | box-shadow: var(--shadow);
128 | padding: 15px;
129 | margin-bottom: 30px;
130 | }
131 |
132 | .dark .box {
133 | border: 1px solid var(--bg-content);
134 | background-color: var(--bg-body);
135 | }
136 |
137 | .box-hover {
138 | transition: transform 0.2s ease-in-out;
139 | }
140 |
141 | .box-hover:hover {
142 | transform: scale(1.05) translateY(-10px);
143 | box-shadow: rgb(0 0 0 / 10%) 0px 15px 30px;
144 | }
145 |
146 | .box-header {
147 | padding: 10px 0 30px;
148 | font-size: 1.25rem;
149 | font-weight: 700;
150 | color: var(--color-txt);
151 | position: relative;
152 | text-transform: uppercase;
153 | }
154 | /* end box */
155 |
156 | /* country dropdown */
157 | .country-select {
158 | position: relative;
159 | }
160 |
161 | .country-select-toggle {
162 | display: flex;
163 | align-items: center;
164 | justify-content: space-between;
165 | font-size: 1.25rem;
166 | padding: 0 15px;
167 | cursor: pointer;
168 | font-weight: 700;
169 | position: relative;
170 | }
171 |
172 | .country-select-list {
173 | position: absolute;
174 | top: calc(100% + 50px);
175 | left: 0;
176 | width: 100%;
177 | max-height: 600px;
178 | overflow-y: scroll;
179 | padding: 15px;
180 | background-color: var(--bg-content);
181 | z-index: 98;
182 | box-shadow: var(--shadow);
183 | border-radius: 10px;
184 | visibility: hidden;
185 | opacity: 0;
186 | transition: all 0.3s ease-in-out;
187 | }
188 |
189 | .country-select-list input {
190 | width: 100%;
191 | border: none;
192 | outline: none;
193 | background-color: #e2e8f0;
194 | padding: 10px;
195 | border-radius: 10px;
196 | }
197 |
198 | .country-select-list.active {
199 | top: calc(100% + 20px);
200 | visibility: visible;
201 | opacity: 1;
202 | }
203 |
204 | .country-item {
205 | padding: 5px 15px;
206 | cursor: pointer;
207 | }
208 |
209 | .country-item:hover {
210 | background-color: var(--bg-hover);
211 | }
212 | /* end country dropdown */
213 |
214 | /* count box */
215 | .count {
216 | padding: 5px;
217 | display: flex;
218 | align-items: center;
219 | justify-content: flex-start;
220 | }
221 |
222 | .count-icon {
223 | --width: 70px;
224 | width: var(--width);
225 | height: var(--width);
226 | border-radius: 50%;
227 | display: grid;
228 | place-items: center;
229 | font-size: 2.5rem;
230 | margin-right: 15px;
231 | }
232 |
233 | .count-info h5 {
234 | font-size: 1.5rem;
235 | }
236 |
237 | .count-info span {
238 | display: inherit;
239 | margin-top: -14px;
240 | text-transform: uppercase;
241 | font-weight: 700;
242 | }
243 |
244 | .count-confirmed .count-icon {
245 | background-color: #ffa0a0;
246 | color: red;
247 | }
248 |
249 | .count-confirmed .count-info h5 {
250 | color: red;
251 | }
252 |
253 | .count-recovered .count-icon {
254 | background-color: #bffabf;
255 | color: green;
256 | }
257 |
258 | .count-recovered .count-info h5 {
259 | color: green;
260 | }
261 |
262 | .count-death .count-icon {
263 | background-color: #e2e8f0;
264 | color: #373c43;
265 | }
266 |
267 | .count-death .count-info h5 {
268 | color: #373c43;
269 | }
270 | /* end count box */
271 |
272 | /* countries table */
273 | .table-countries {
274 | width: 100%;
275 | border-spacing: 0;
276 | }
277 |
278 | .table-countries thead tr th,
279 | .table-countries tbody tr td {
280 | border-bottom: 1px solid var(--bg-body);
281 | }
282 |
283 | .table-countries th {
284 | padding: 0.5rem;
285 | }
286 |
287 | .table-countries td {
288 | font-weight: 0.9rem;
289 | padding: 0.5rem;
290 | width: 25%;
291 | text-align: center;
292 | }
293 |
294 | .table-countries tbody tr:hover {
295 | background-color: var(--bg-hover);
296 | }
297 | /* end countries table */
298 |
299 | /* loader */
300 | .loader {
301 | position: fixed;
302 | z-index: 99;
303 | top: 0;
304 | left: 0;
305 | width: 100%;
306 | height: 100vh;
307 | display: grid;
308 | place-items: center;
309 | background-color: var(--bg-body);
310 | font-size: 10rem;
311 | visibility: hidden;
312 | opacity: 0;
313 | transition: all 0.3s ease-in-out;
314 | }
315 |
316 | .loading .loader {
317 | visibility: visible;
318 | opacity: 1;
319 | }
320 | /* end loader */
321 |
322 | /* footer */
323 | .footer {
324 | background-color: var(--bg-content);
325 | box-shadow: var(--shadow);
326 | font-size: 1.25rem;
327 | text-align: center;
328 | padding: 2rem;
329 | }
330 | /* end footer */
331 |
332 | @media only screen and (max-width: 1280px) {
333 | body {
334 | font-size: 14px;
335 | }
336 | }
337 |
338 | @media only screen and (max-width: 850px) {
339 | body {
340 | font-size: 12px;
341 | }
342 | }
343 |
--------------------------------------------------------------------------------
/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 | Covid tracker
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 | Global
51 |
52 |
53 |
54 |
55 |
56 |
57 |
58 |
59 |
60 |
61 |
62 |
63 |
64 |
65 |
66 |
67 |
68 |
69 |
70 |
71 |
72 |
73 |
74 |
75 |
76 |
123,456,789
77 | confirmed
78 |
79 |
80 |
81 |
82 |
83 |
84 |
85 |
86 |
87 |
88 |
89 |
90 |
91 |
123,456,789
92 | recovered
93 |
94 |
95 |
96 |
97 |
98 |
99 |
100 |
101 |
102 |
103 |
104 |
105 |
106 |
123,456,789
107 | deaths
108 |
109 |
110 |
111 |
112 |
113 |
114 |
115 |
125 |
126 |
127 |
128 |
129 |
130 |
133 |
134 |
135 |
136 |
137 |
138 |
139 |
140 |
141 |
142 |
143 |
146 |
147 |
148 |
149 |
150 |
151 |
152 |
153 |
154 |
155 |
156 |
157 |
158 |
159 |
167 |
168 |
169 |
170 |
178 |
179 |
180 |
181 |
182 |
185 |
186 |
187 |
188 |
189 | |
190 | Country
191 | |
192 | Confirmed |
193 | Recovered |
194 | Deaths |
195 |
196 |
197 |
198 |
199 | | test |
200 | test |
201 | test |
202 | test |
203 |
204 |
205 |
206 |
207 |
208 |
209 |
210 |
211 |
212 |
213 |
214 |
215 |
216 |
217 |
218 |
223 |
224 |
225 |
226 |
227 |
228 |
229 |
230 |
231 |
232 |
233 |
234 |
235 |
236 |
237 |
238 |
239 |
--------------------------------------------------------------------------------
/static/js/app.js:
--------------------------------------------------------------------------------
1 | const COLORS = {
2 | confirmed: '#ff0000',
3 | recovered: '#008000',
4 | deaths: '#373c43',
5 | }
6 |
7 | const CASE_STATUS = {
8 | confirmed: 'confirmed',
9 | recovered: 'recovered',
10 | deaths: 'deaths',
11 | }
12 |
13 | let body = document.querySelector('body')
14 |
15 | let countries_list
16 |
17 | let all_time_chart, days_chart, recover_rate_chart
18 |
19 | window.onload = async () => {
20 | console.log('ready...')
21 |
22 | // only init chart on page loaded first time
23 |
24 | initTheme()
25 |
26 | initContryFilter()
27 |
28 | await initAllTimesChart()
29 |
30 | await initDaysChart()
31 |
32 | await initRecoveryRate()
33 |
34 | await loadData('Global')
35 |
36 | await loadCountrySelectList()
37 |
38 | document.querySelector('#country-select-toggle').onclick = () => {
39 | document.querySelector('#country-select-list').classList.toggle('active')
40 | }
41 | }
42 |
43 | loadData = async (country) => {
44 | startLoading()
45 |
46 | await loadSummary(country)
47 |
48 | await loadAllTimeChart(country)
49 |
50 | await loadDaysChart(country)
51 |
52 | endLoading()
53 | }
54 |
55 | startLoading = () => {
56 | body.classList.add('loading')
57 | }
58 |
59 | endLoading = () => {
60 | body.classList.remove('loading')
61 | }
62 |
63 | isGlobal = (country) => {
64 | return country === 'Global'
65 | }
66 |
67 | numberWithCommas = (x) => {
68 | return x.toString().replace(/\B(?=(\d{3})+(?!\d))/g, ",")
69 | }
70 |
71 | showConfirmedTotal = (total) => {
72 | document.querySelector('#confirmed-total').textContent = numberWithCommas(total)
73 | }
74 |
75 | showRecoveredTotal = (total) => {
76 | document.querySelector('#recovered-total').textContent = numberWithCommas(total)
77 | }
78 |
79 | showDeathsTotal = (total) => {
80 | document.querySelector('#death-total').textContent = numberWithCommas(total)
81 | }
82 |
83 | loadSummary = async (country) => {
84 |
85 | // country = Slug
86 |
87 | let summaryData = await covidApi.getSummary()
88 |
89 | let summary = summaryData.Global
90 |
91 | if (!isGlobal(country)) {
92 | summary = summaryData.Countries.filter(e => e.Slug === country)[0]
93 | }
94 |
95 | showConfirmedTotal(summary.TotalConfirmed)
96 | showRecoveredTotal(summary.TotalRecovered)
97 | showDeathsTotal(summary.TotalDeaths)
98 |
99 | // load recovery rate
100 |
101 | await loadRecoveryRate(Math.floor(summary.TotalRecovered / summary.TotalConfirmed * 100))
102 |
103 | // load countries table
104 |
105 | let casesByCountries = summaryData.Countries.sort((a, b) => b.TotalConfirmed - a.TotalConfirmed)
106 |
107 | let table_countries_body = document.querySelector('#table-countries tbody')
108 | table_countries_body.innerHTML = ''
109 |
110 | for (let i = 0; i < 10; i++) {
111 | let row = `
112 |
113 | | ${casesByCountries[i].Country} |
114 | ${numberWithCommas(casesByCountries[i].TotalConfirmed)} |
115 | ${numberWithCommas(casesByCountries[i].TotalRecovered)} |
116 | ${numberWithCommas(casesByCountries[i].TotalDeaths)} |
117 |
118 | `
119 | table_countries_body.innerHTML += row
120 | }
121 | }
122 |
123 | initAllTimesChart = async () => {
124 | let options = {
125 | chart: {
126 | type: 'line'
127 | },
128 | colors: [COLORS.confirmed, COLORS.recovered, COLORS.deaths],
129 | series: [],
130 | xaxis: {
131 | categories: [],
132 | labels: {
133 | show: false
134 | }
135 | },
136 | grid: {
137 | show: false
138 | },
139 | stroke: {
140 | curve: 'smooth'
141 | }
142 | }
143 |
144 | all_time_chart = new ApexCharts(document.querySelector('#all-time-chart'), options)
145 |
146 | all_time_chart.render()
147 | }
148 |
149 | renderData = (country_data) => {
150 | let res = []
151 | country_data.forEach(e => {
152 | res.push(e.Cases)
153 | })
154 | return res
155 | }
156 |
157 | renderWorldData = (world_data, status) => {
158 | let res = []
159 | world_data.forEach(e => {
160 | switch (status) {
161 | case CASE_STATUS.confirmed:
162 | res.push(e.TotalConfirmed)
163 | break
164 | case CASE_STATUS.recovered:
165 | res.push(e.TotalRecovered)
166 | break
167 | case CASE_STATUS.deaths:
168 | res.push(e.TotalDeaths)
169 | break
170 | }
171 | })
172 | return res
173 | }
174 |
175 | loadAllTimeChart = async (country) => {
176 | let labels = []
177 |
178 | let confirm_data, recovered_data, deaths_data
179 |
180 | if (isGlobal(country)) {
181 | let world_data = await covidApi.getWorldAllTimeCases()
182 |
183 | world_data.sort((a, b) => new Date(a.Date) - new Date(b.Date))
184 |
185 | world_data.forEach(e => {
186 | let d = new Date(e.Date)
187 | labels.push(`${d.getFullYear()}-${d.getMonth() + 1}-${d.getDate()}`)
188 | })
189 |
190 | confirm_data = renderWorldData(world_data, CASE_STATUS.confirmed)
191 | recovered_data = renderWorldData(world_data, CASE_STATUS.recovered)
192 | deaths_data = renderWorldData(world_data, CASE_STATUS.deaths)
193 | } else {
194 | let confirmed = await covidApi.getCountryAllTimeCases(country, CASE_STATUS.confirmed)
195 | let recovered = await covidApi.getCountryAllTimeCases(country, CASE_STATUS.recovered)
196 | let deaths = await covidApi.getCountryAllTimeCases(country, CASE_STATUS.deaths)
197 |
198 | confirm_data = renderData(confirmed)
199 | recovered_data = renderData(recovered)
200 | deaths_data = renderData(deaths)
201 |
202 | confirmed.forEach(e => {
203 | let d = new Date(e.Date)
204 | labels.push(`${d.getFullYear()}-${d.getMonth() + 1}-${d.getDate()}`)
205 | })
206 | }
207 |
208 | let series = [{
209 | name: 'Confirmed',
210 | data: confirm_data
211 | }, {
212 | name: 'Recovered',
213 | data: recovered_data
214 | }, {
215 | name: 'Deaths',
216 | data: deaths_data
217 | }]
218 |
219 | all_time_chart.updateOptions({
220 | series: series,
221 | xaxis: {
222 | categories: labels
223 | }
224 | })
225 | }
226 |
227 | initDaysChart = async () => {
228 | let options = {
229 | chart: {
230 | type: 'line'
231 | },
232 | colors: [COLORS.confirmed, COLORS.recovered, COLORS.deaths],
233 | series: [],
234 | xaxis: {
235 | categories: [],
236 | labels: {
237 | show: false
238 | }
239 | },
240 | grid: {
241 | show: false
242 | },
243 | stroke: {
244 | curve: 'smooth'
245 | }
246 | }
247 |
248 | days_chart = new ApexCharts(document.querySelector('#days-chart'), options)
249 |
250 | days_chart.render()
251 | }
252 |
253 | loadDaysChart = async (country) => {
254 | let labels = []
255 |
256 | let confirm_data, recovered_data, deaths_data
257 |
258 | if (isGlobal(country)) {
259 | let world_data = await covidApi.getWorldDaysCases()
260 |
261 | world_data.sort((a, b) => new Date(a.Date) - new Date(b.Date))
262 |
263 | world_data.forEach(e => {
264 | let d = new Date(e.Date)
265 | labels.push(`${d.getFullYear()}-${d.getMonth() + 1}-${d.getDate()}`)
266 | })
267 |
268 | confirm_data = renderWorldData(world_data, CASE_STATUS.confirmed)
269 | recovered_data = renderWorldData(world_data, CASE_STATUS.recovered)
270 | deaths_data = renderWorldData(world_data, CASE_STATUS.deaths)
271 | } else {
272 | let confirmed = await covidApi.getCountryDaysCases(country, CASE_STATUS.confirmed)
273 | let recovered = await covidApi.getCountryDaysCases(country, CASE_STATUS.recovered)
274 | let deaths = await covidApi.getCountryDaysCases(country, CASE_STATUS.deaths)
275 |
276 | confirm_data = renderData(confirmed)
277 | recovered_data = renderData(recovered)
278 | deaths_data = renderData(deaths)
279 |
280 | confirmed.forEach(e => {
281 | let d = new Date(e.Date)
282 | labels.push(`${d.getFullYear()}-${d.getMonth() + 1}-${d.getDate()}`)
283 | })
284 | }
285 |
286 | let series = [{
287 | name: 'Confirmed',
288 | data: confirm_data
289 | }, {
290 | name: 'Recovered',
291 | data: recovered_data
292 | }, {
293 | name: 'Deaths',
294 | data: deaths_data
295 | }]
296 |
297 | days_chart.updateOptions({
298 | series: series,
299 | xaxis: {
300 | categories: labels
301 | }
302 | })
303 | }
304 |
305 | initRecoveryRate = async () => {
306 | let options = {
307 | chart: {
308 | type: 'radialBar',
309 | height: '350'
310 | },
311 | series: [],
312 | labels: ['Recovery rate'],
313 | colors: [COLORS.recovered]
314 | }
315 |
316 | recover_rate_chart = new ApexCharts(document.querySelector('#recover-rate-chart'), options)
317 |
318 | recover_rate_chart.render()
319 | }
320 |
321 | loadRecoveryRate = async (rate) => {
322 | // use updateSeries
323 | recover_rate_chart.updateSeries([rate])
324 | }
325 |
326 | // dark mode switch
327 |
328 | initTheme = () => {
329 | let dark_mode_switch = document.querySelector('#darkmode-switch')
330 |
331 | dark_mode_switch.onclick = () => {
332 | dark_mode_switch.classList.toggle('dark')
333 | body.classList.toggle('dark')
334 |
335 | setDarkChart(body.classList.contains('dark'))
336 | }
337 | }
338 |
339 | // set dark mode for charts
340 | setDarkChart = (dark) => {
341 | let theme = {
342 | theme: {
343 | mode: dark ? 'dark' : 'light'
344 | }
345 | }
346 | all_time_chart.updateOptions(theme)
347 | days_chart.updateOptions(theme)
348 | recover_rate_chart.updateOptions(theme)
349 | }
350 |
351 | // country select
352 | renderCountrySelectList = (list) => {
353 | let country_select_list = document.querySelector('#country-select-list')
354 | country_select_list.querySelectorAll('div').forEach(e => e.remove())
355 | list.forEach(e => {
356 | let item = document.createElement('div')
357 | item.classList.add('country-item')
358 | item.textContent = e.Country
359 |
360 | item.onclick = async () => {
361 | document.querySelector('#country-select span').textContent = e.Country
362 | country_select_list.classList.toggle('active')
363 | await loadData(e.Slug)
364 | }
365 |
366 | country_select_list.appendChild(item)
367 | })
368 | }
369 |
370 | loadCountrySelectList = async () => {
371 | let summaryData = await covidApi.getSummary()
372 |
373 | countries_list = summaryData.Countries
374 |
375 | let country_select_list = document.querySelector('#country-select-list')
376 |
377 | let item = document.createElement('div')
378 | item.classList.add('country-item')
379 | item.textContent = 'Global'
380 | item.onclick = async () => {
381 | document.querySelector('#country-select span').textContent = 'Global'
382 | country_select_list.classList.toggle('active')
383 | await loadData('Global')
384 | }
385 | country_select_list.appendChild(item)
386 |
387 | renderCountrySelectList(countries_list)
388 | }
389 |
390 | // country filter
391 | initContryFilter = () => {
392 | let input = document.querySelector('#country-select-list input')
393 | input.onkeyup = () => {
394 | let filtered = countries_list.filter(e => e.Country.toLowerCase().includes(input.value))
395 | renderCountrySelectList(filtered)
396 | }
397 | }
--------------------------------------------------------------------------------