├── .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 | !["Coronavirus (COVID-19) Tracker Dashboard"](https://user-images.githubusercontent.com/67447840/113251512-971b0680-92ec-11eb-96cc-b928a6521e53.png "Coronavirus (COVID-19) 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 |
116 |
117 |
118 | all times 119 |
120 |
121 |
122 |
123 |
124 |
125 | 126 | 127 | 128 |
129 |
130 |
131 | what is covid-19 132 |
133 |
134 | 135 |
136 |
137 |
138 | 139 | 140 | 141 |
142 |
143 |
144 | how to protect yourself 145 |
146 |
147 | 148 |
149 |
150 |
151 | 152 |
153 |
154 | 155 | 156 | 157 |
158 | 159 |
160 |
161 | last 30 days 162 |
163 |
164 |
165 |
166 |
167 | 168 | 169 | 170 |
171 |
172 | recovery rate 173 |
174 |
175 |
176 |
177 |
178 | 179 | 180 | 181 |
182 |
183 | top countries affected 184 |
185 |
186 | 187 | 188 | 189 | 192 | 193 | 194 | 195 | 196 | 197 | 198 | 199 | 200 | 201 | 202 | 203 | 204 | 205 |
190 | Country 191 | ConfirmedRecoveredDeaths
testtesttesttest
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 | } --------------------------------------------------------------------------------