<div id="app">
			<notify
				:show="getNotifyDetails.show"
				:type="getNotifyDetails.type"
				:message="getNotifyDetails.message"
			></notify>

			<div class="main" :data-show-map="show_countries_list">
				<div class="sidebar">
					<div class="top-nav">
						<div class="current-location">
							<div class="flag-wrapper" v-if="getCurrentLocation !== 'WO'">
								<flag :iso="getCurrentLocation.toLowerCase()"></flag>
							</div>
							<div class="flag-wrapper world" v-if="getCurrentLocation === 'WO'">
								<i class="flag fas fa-globe"></i>
							</div>

							<div class="location-title">{{ getCountriesList[getCurrentLocation] }}</div>
						</div>
					</div>
					<div class="cases-nav">
						<div
							class="case-type confirmed"
							@click="changeCaseType('confirmed')"
							:class="{ active: getDisplayedCasesType == 'confirmed' }"
						>
							<div class="case-type-icon">
								<i class="fas fa-virus"></i>
							</div>
							<div class="case-type-inner">
								<div class="case-title">Confiremd</div>
								<div class="case-count">{{ confirmedCount }}</div>
							</div>
						</div>
						<div
							class="case-type recovered"
							@click="changeCaseType('recovered')"
							:class="{ active: getDisplayedCasesType == 'recovered' }"
						>
							<div class="case-type-icon">
								<i class="fas fa-first-aid"></i>
							</div>
							<div class="case-type-inner">
								<div class="case-title">Recovered</div>
								<div class="case-count">{{ recoveredCount }}</div>
							</div>
						</div>
						<div
							class="case-type deaths"
							@click="changeCaseType('deaths')"
							:class="{ active: getDisplayedCasesType == 'deaths' }"
						>
							<div class="case-type-icon">
								<i class="fas fa-biohazard"></i>
							</div>
							<div class="case-type-inner">
								<div class="case-title">Deaths</div>
								<div class="case-count">{{ deathsCount }}</div>
							</div>
						</div>
					</div>
					<div class="bottom-nav">
						<div class="fill-screen" @click="is_full_screen = !is_full_screen">
							<i class="fas fa-expand" v-if="is_full_screen == false"></i>
							<i class="fas fa-compress" v-if="is_full_screen == true"></i>
						</div>
						<div
							class="locations-list"
							:class="{ active: show_countries_list == true }"
							@click="show_countries_list = !show_countries_list"
						>
							<i class="fas fa-map-marked-alt"></i>
						</div>
					</div>
					<div class="info-nav">
						<div
							class="copyright-button"
							:class="{ active: show_copyright == true }"
							@click="show_copyright = !show_copyright"
						>
							<i class="far fa-copyright"></i>
						</div>

						<a target="_blank" href="https://codepen.io/khr2003"> <i class="fab fa-codepen"></i></a>

						<a
						target="_top"
							class="twitter"
							 href="https://twitter.com/intent/follow?screen_name=kalimahapps&tw_p=followbutton"
							><i class="fab fa-twitter"></i
						></a>
					</div>

					<div class="copyright-wrapper" v-if="show_copyright">
						<a target="_blank" :href="link" v-for="( link, text)  in copyright_data">{{text}}</a>
					</div>
				</div>

				<CountriesList :class="{ active: show_countries_list == true }"></CountriesList>

				<div class="content-wrapper">
					<WorldMap></WorldMap>

					<div class="series-container">
						<series></series>
					</div>
				</div>
			</div>
			<location-tooltip></location-tooltip>
		</div>

		<!-- Countries list -->
		<template id="countries-list-template">
			<div id="countries-list-wrapper">
				<div class="countries-search-wrapper">
					<input
						type="text"
						class="search-countries"
						placeholder="Search Countries ..."
						@input="filterCountries"
					/>
				</div>
				<div class="countries-list-container">
					<div class="countries-list" :class="{'is-loading': getLocationLoading != false}">
						<div
							class="country-wrapper"
							:class="{ active: key == getCurrentLocation, loading: getLocationLoading == key }"
							v-for="(country, key) in filterCountriesList"
							:key="`country-${key}`"
							:data-loading="!is_loading[key] || is_loading[key] == 'undefined' ? 'no' : 'yes'"
							@click="selectCountry(key)"
						>
							<loader-bar
								v-if="getLocationLoading == key"
								:loading-indeterminant="true"
							></loader-bar>
							<div class="flag-wrapper" v-if="key.toLowerCase() !== 'wo'">
								<flag :iso="key.toLowerCase()"></flag>
							</div>
							<div class="flag-wrapper world" v-if="key.toLowerCase() === 'wo'">
								<i class="flag fas fa-globe"></i>
							</div>
							<div class="country-content">
								<div class="country-name" :title="country.name">{{ country.name }}</div>
								<div class="country-count-diff" :class="country.diff.type">
									<span class="type-operator">
										<span class="extra" v-if="country.diff.type == 'extra'">+</span>
										<span class="extra" v-if="country.diff.type == 'less'">-</span>
									</span>
									<span class="diff-count">{{ country.diff.value.toLocaleString('en-US') }}</span>
								</div>
								<div class="country-count">{{ country.value.toLocaleString('en-US') }}</div>
							</div>
						</div>
					</div>
				</div>
			</div>
		</template>

		<!-- Notify Template -->
		<template id="notify-template">
			<transition name="fadeslide">
				<div id="notification" :class="type" v-if="show_notify">
					<div class="icon">
						<i class="fas fa-exclamation-triangle" v-if="type == 'error'"></i>
						<i class="fas fa-check-circle" v-if="type == 'success'"></i>
					</div>
					<div class="text">{{message}}</div>
				</div>
			</transition>
		</template>

		<!-- Flag Template -->
		<template id="flag-template">
			<span class="flag">
				<img :src="flag" />
			</span>
		</template>

		<!-- Loader Bar Template -->
		<template id="loader-bar-template">
			<div class="loader-wrapper">
				<div class="loader-bar" :class="{ indeterminant: loadingIndeterminant == true }">
					<div class="loader-bar-loaded" :style="{ width: `${loadedPercentage}%` }"></div>
				</div>
				<div class="loader-text">{{ loadingText }}</div>
			</div>
		</template>

		<!-- Worldmap Template -->
		<template id="worldmap-template">
			<div class="map-countries-container">
				<loader-bar
					v-if="loading == true"
					:loading-text="loading_text"
					:loaded-percentage="loaded_percentage"
				></loader-bar>

				<loader-bar
					v-if="getLocationLoading != false"
					loading-text="Loading Location Data"
					:loading-indeterminant="true"
				></loader-bar>

				<div
					class="map-container"
					:data-status="getCurrentLocation != 'WO' ? 'single-location' : 'world'"
					ref="world_map_container"
					:style="getMapStyle"
					@mouseleave="stopRotateMap"
					@mousemove="rotateMap"
				>
					<div
						class="reset-map"
						:class="{ show: getCurrentLocation != 'WO' }"
						@click="resetLocation()"
					>
						<i class="fas fa-home"></i>
					</div>
					<svg
						xmlns="http://www.w3.org/2000/svg"
						version="1.1"
						ref="world_map"
						:view-box.camel="viewbox"
						:class="{loading: getLocationLoading != false}"
					>
						<g
							v-for="(data, key) in getWorlMapWithColors"
							:id="key"
							:key="key"
							:fill="data.color"
							:ref="`location-${key}`"
							stroke="white"
							:data-count="data.value"
							@click="selectLocation(key)"
							@mouseenter="handleMoustEnter($event, key, data.value)"
							@mouseleave="handleMoustOut"
							@mousemove="handleMouseMove"
							:class="{ active: key == getCurrentLocation, 'no-select': typeof getDayCaseByType[key] == 'undefined' }"
							:data-status="getCurrentLocation == key ? 'selected' : ''"
						>
							<path
								:d="data.path"
								:key="`path-${key}`"
								stroke-width="0.3"
								stroke-opacity="0.5"
							></path>
						</g>
					</svg>
				</div>
			</div>
		</template>

		<!-- Location details tooltip -->
		<template id="location-tooltip-template">
			<div id="location-tooltip" role="tooltip" ref="tooltip" :style="getTooltipStyle">
				<div class="tooltip-inner">
					<div class="tooltip-title">{{ this.getTooltipData.title }}</div>
					<div class="tooltip-content" v-if="'count' in this.getTooltipData">
						<div class="cases-type">
							<span>{{ this.getTooltipData.count }}</span>
							<span>{{ this.getTooltipData.type }}</span>
						</div>
						<div class="cases-diff">
							<span>{{ this.getTooltipData.diff }}</span>
							<span>Daily {{ this.getTooltipData.difftext }}</span>
						</div>
						<div class="cases-percentage">
							<span>{{ this.getTooltipData.percentage }}%</span>
							<span>Daily change</span>
						</div>
					</div>
					<div v-else>No data available</div>
				</div>
				<div class="arrow" data-popper-arrow></div>
			</div>
		</template>

		<!-- Series Tempmate -->
		<template id="series-template">
			<div class="series-data-wrapper">
				<loader-bar
					v-if="series_loading == true"
					:loading-indeterminant="true"
					loading-text="Loading Series"
				></loader-bar>
				<div class="series-data-options" v-if="series_loading == false">
					<div class="bar-line-options">
						<span class="bar" @click="chart_type = 'bar'" :class="{ active: chart_type == 'bar' }"
							>Bar</span
						>
						<span
							class="line"
							@click="chart_type = 'line'"
							:class="{ active: chart_type == 'line' }"
							>Line</span
						>
					</div>
					<div class="daily-weekly">
						<span
							class="daily"
							@click="series_type = 'daily'"
							:class="{ active: series_type == 'daily' }"
							>Daily</span
						>
						<span
							class="weekly"
							@click="series_type = 'weekly'"
							:class="{ active: series_type == 'weekly' }"
							>Weekly</span
						>
					</div>
					<div class="chart-data-representation">
						<span
							class="change"
							@click="series_representation = 'change'"
							:class="{ active: series_representation == 'change' }"
							>Change</span
						>
						<span
							class="cumulative"
							@click="series_representation = 'cumulative'"
							:class="{ active: series_representation == 'cumulative' }"
							>Cumulative</span
						>
						<span
							class="log"
							@click="series_representation = 'log'"
							:class="{ active: series_representation == 'log' }"
							>Log</span
						>
					</div>
				</div>
				<div class="series-data" v-show="series_loading == false">
					<canvas ref="chart"></canvas>
				</div>
				<div id="chartjs-tooltip">
					<div class="title"></div>
					<div class="count-wrapper">
						<div class="count-total-type"></div>
						<div class="count-total-number"></div>
					</div>
				</div>
			</div>
		</template>
$transition-ease: all 0.2s ease-in-out;
$transition: all 0.4s cubic-bezier(0.5, 0, 1, 0.81);
:root {
	--current-hue: 45;
	--confirmed-hue: 45;
	--recovered-hue: 164;
	--deaths-hue: 353;
	--compl-hue: calc(var(--current-hue) - 15);
	--map-rotate-x: 0deg;
	--map-rotate-y: 0deg;
}

@mixin scrollbars($size, $foreground-color, $background-color: mix($foreground-color, white, 50%)) {
	// For Google Chrome
	&::-webkit-scrollbar {
		width: $size;
		height: $size;
	}

	&::-webkit-scrollbar-thumb {
		background: $foreground-color;
	}

	&::-webkit-scrollbar-track {
		background: $background-color;
	}

	// For Internet Explorer
	& {
		scrollbar-face-color: $foreground-color;
		scrollbar-track-color: $background-color;
	}
}

@import url('https://cdnjs.cloudflare.com/ajax/libs/font-awesome/5.15.1/css/all.min.css');
@import url('https://fonts.googleapis.com/css2?family=Fira+Sans:wght@200;400;500&display=swap');

*,
*::before,
*::after {
	box-sizing: border-box;
}
#app {
	font-family: 'Fira Sans', sans-serif;
	//	font-family: Avenir, Helvetica, Arial, sans-serif;
	-webkit-font-smoothing: antialiased;
	-moz-osx-font-smoothing: grayscale;
	text-align: center;
	color: #2c3e50;
	width: 100vw;
	height: 100vh;
}
body {
	background-color: #f3f4f6;
	min-height: 100vh;
	display: flex;
	align-items: center;
	justify-content: center;
	user-select: none;
	margin: 0;
	//background: linear-gradient(to right, rgba(#2193b0, 0.5), rgba(#2193b0, 0.1));
}

#notification {
	position: fixed;
	top: 1vh;
	right: 1vw;
	border-left: 5px solid;
	min-width: 200px;
	background-color: #e74c3c;
	min-height: 50px;
	z-index: 50;
	display: flex;
	align-items: center;
	padding: 0.8em 1em;

	&.error {
		border-color: darken(#e74c3c, 15%);
		color: darken(#e74c3c, 50%);
	}

	.icon {
		padding-right: 0.5em;
		font-size: 1.3em;
	}
	.text {
		font-size: 0.9em;
	}
}
.main {
	height: 100%;
	display: flex;
	position: relative;
	background: url('https://www.toptal.com/designers/subtlepatterns/patterns/dot-grid.png');
	overflow: hidden;
	&[data-show-map='true'] {
		.content-wrapper {
			transform: scale(0.9) translateX(10%);
		}
	}

	.reset-map {
		position: absolute;
		bottom: 1em;
		right: 1em;
		font-size: 1.4em;
		padding: 0.3em 0.4em;
		z-index: 10;
		transition: $transition;
		opacity: 0;
		cursor: pointer;
		background-color: rgba(255, 255, 255, 0.5);
		&:hover {
			background-color: rgba(255, 255, 255, 0.8);
		}
		&.show {
			opacity: 1;
		}
	}
}

.top-nav {
	.current-location {
		padding-top: 5em;
		margin-bottom: 5em;

		.flag-wrapper {
			.flag {
				border-radius: 3px;
				width: 40%;
				overflow: hidden;
			}

			&.world {
				display: flex;
				justify-content: center;
				.flag {
					background-color: white;
					padding: 0.3em;
					font-size: 1.8em;
					width: 90px;
					height: 60px;
					display: flex;
					justify-content: center;
					align-items: center;
				}
			}
		}
		.location-title {
			color: white;
			margin-top: 0.5em;
		}
	}
}
.map-countries-container {
	height: 70%;
	overflow: hidden;
	transition: $transition;
	position: relative;
	max-height: 70%;
}

.sidebar {
	justify-content: center;
	display: flex;
	flex-direction: column;
	background-image: linear-gradient(60deg, #21425f, lighten(#21425f, 10%));
	width: 250px;
	padding: 1em;
	z-index: 5;
	justify-content: space-between;
	position: relative;
	overflow: hidden;

	&::before,
	&::after {
		background-image: radial-gradient(lighten(#21425f, 25%), lighten(#21425f, 40%));
		content: '';
		position: absolute;
		width: 40em;
		height: 40em;
		z-index: -1;
		top: -5%;
		left: 50%;
		border-radius: 50%;
		transform: translateX(-50%);
		filter: blur(50px);
		opacity: 0.3;
	}

	&::after {
		top: 80%;
		transform: translateX(-20%) rotate(180deg);
	}
	.case-type {
		&.confirmed {
			.case-type-icon {
				color: hsl(var(--confirmed-hue), 100%, 50%);
				background-color: hsl(var(--confirmed-hue), 100%, 95%);
			}
		}
		&.deaths {
			.case-type-icon {
				color: hsl(var(--deaths-hue), 100%, 50%);
				background-color: hsl(var(--deaths-hue), 100%, 95%);
			}
		}
		&.recovered {
			.case-type-icon {
				color: hsl(var(--recovered-hue), 60%, 34%);
				background-color: hsl(var(--recovered-hue), 100%, 95%);
			}
		}

		&.active {
			background-color: rgba(255, 225, 255, 0.2);
			.case-type-inner {
				.case-title {
					font-size: 1em;
					color: white;
				}

				.case-count {
					font-size: 0.7em;
				}
			}
		}

		&:hover:not(.active) {
			background-color: rgba(255, 225, 255, 0.1);
			i {
				font-size: 1.1em;
			}
			.case-title {
				font-size: 1em;
				color: white;
			}

			.case-count {
				font-size: 0.7em;
			}
		}
		color: white;
		font-size: 1rem;
		padding: 1em;
		margin-bottom: 0.5em;
		display: flex;
		cursor: pointer;
		user-select: none;
		transition: $transition-ease;
		border-radius: 6px;
		height: 70px;

		.case-type-icon {
			padding: 1em;
			width: 25%;
			display: flex;
			justify-content: center;
			align-items: center;
			background-color: white;
			border-radius: 5px;
			i {
				position: absolute;
				transition: $transition-ease;
			}
		}
		.case-type-inner {
			flex-grow: 1;
			text-align: left;
			margin-left: 1em;
			.case-title {
				font-size: 0.7em;
				text-transform: uppercase;
				font-weight: normal;
				color: #b0b0b0;
				transition: $transition-ease;
			}
			.case-count {
				font-size: 1.1em;
				font-feature-settings: 'tnum';
				font-variant-numeric: tabular-nums;
				transition: $transition-ease;
			}
		}
	}

	.bottom-nav {
		display: flex;
		justify-content: space-around;
		margin-top: auto;
		> div {
			cursor: pointer;
			padding: 1.4em;
			color: white;
			position: relative;
			font-size: 1.5em;
			border-radius: 5px;
			transition: $transition-ease;
			&:hover:not(.active) {
				background-color: rgba(255, 225, 255, 0.1);
			}
			&.active {
				background-color: rgba(255, 225, 255, 0.2);
			}

			i {
				position: absolute;
				color: white;
				left: 50%;
				top: 50%;
				transform: translate(-50%, -50%);
			}
		}
	}
	.info-nav {
		display: flex;
		justify-content: space-around;
		color: white;
		margin-top: 1em;
		padding-top: 1em;
		border-top: 1px dashed rgba(255, 225, 255, 0.3);

		> div,
		> a {
			padding: 0.5em 1em;
			border-radius: 4px;
			transition: all 0.2s ease-in-out;
			cursor: pointer;
			color: white;
			font-size: 1.2em;
			display: flex;
			justify-content: center;
			align-items: center;
			text-decoration: none;
			&.twitter {
				color: #1c9cea;
				background-color: rgba(255, 255, 255, 0.8);
				&:hover {
					background-color: rgba(255, 255, 255, 1);
				}
			}
			&:hover {
				background-color: rgba(255, 225, 255, 0.1);
			}

			&.active {
				background-color: rgba(255, 225, 255, 0.2);
			}
		}
	}

	.copyright-wrapper {
		background-color: rgba(0, 0, 0, 0.2);
		margin: 1em -1em -1em -1em;
		padding: 1em;
		text-align: left;
		background-image: linear-gradient(to bottom, rgba(0, 0, 0, 0.1) 0, transparent 2%);
		overflow: auto;
		a {
			display: block;
			font-size: 0.8em;
			margin: 0.2em 0;
			padding: 0.4em 0.8em;
			color: white;
			text-decoration: none;
			transition: all 0.2s;
			border-radius: 2px;

			&::after {
				font-family: 'Font Awesome 5 Free';
				font-weight: 900;
				-webkit-font-smoothing: antialiased;
				display: inline-block;
				font-style: normal;
				font-variant: normal;
				text-rendering: auto;
				line-height: 1;
				font-size: 0.8em;
				margin-left: 1em;
			}
			&:hover {
				background-color: rgba(255, 225, 255, 0.1);

				&::after {
					content: '\f35d';
				}
			}
		}
	}
}

.content-wrapper {
	height: 100%;
	flex-grow: 1;
	transition: $transition;
	position: relative;
	padding: 2em;
	.series-container {
		height: 40%;
		position: absolute;
		bottom: 0;
		z-index: 1;
		width: 100%;
		padding: inherit;
		left: 0;
		transition: $transition;
	}
}

.fadeslide-enter-active {
	animation: bounce-in 0.5s;
}
.fadeslide-leave-active {
	animation: bounce-in 0.5s reverse;
}
@keyframes bounce-in {
	0% {
		transform: translateX(10%);
		opacity: 0;
	}
	100% {
		transform: translateX(0%);
		opacity: 1;
	}
}

#countries-list-wrapper {
	height: 100%;
	background-color: #e1ebf4;
	width: 350px;
	transform: translateX(-50%);
	z-index: 3;
	position: absolute;
	transition: $transition;
	left: 0;
	display: flex;
	flex-direction: column;
	&.active {
		transform: translateX(0);
		left: 250px;
	}

	.countries-search-wrapper {
		padding: 1em 2em;

		input {
			border: 0px solid;
			background-color: rgba(255, 255, 255, 0.6);
			width: 100%;
			border-radius: 5px;
			transition: $transition-ease;
			font-size: 0.8em;
			padding: 1em 1em;
			outline: 0;

			&:focus {
				background-color: white;
			}
		}
	}

	.countries-list-container {
		height: 100%;
		display: flex;
		flex-direction: column;
		overflow: auto;
		padding: 1em;
		@include scrollbars(6px, #396791, rgba(#396791, 0.2));
		.countries-list {
			height: 100%;
			&.is-loading {
				.country-wrapper {
					cursor: default;

					&:hover {
						background-color: initial;
					}
					.country-content,
					.flag-wrapper {
						opacity: 0.6;
					}
					&.loading {
						.country-content,
						.flag-wrapper {
							opacity: 0.1;
							filter: blur(1px);
						}
					}
				}
			}
			.country-wrapper {
				display: flex;
				font-size: 0.8rem;
				padding: 1em;
				width: 100%;
				cursor: pointer;
				transition: all 0.3s;
				position: relative;
				&:hover {
					background-color: hsla(var(--current-hue), 30%, 80%, 0.5);
				}
				&.active {
					background-color: hsla(var(--current-hue), 30%, 70%, 0.8);
				}
				.flag-wrapper {
					font-size: 2em;
					transition: all 0.2s;
				}

				.country-content {
					display: grid;
					grid-template-columns: repeat(2, 1fr);
					grid-template-rows: repeat(2, 1fr);
					text-align: left;
					flex-grow: 1;
					margin-left: 1em;
					transition: all 0.2s;
					.country-name {
						grid-column: 1/2;
						white-space: nowrap;
						text-overflow: ellipsis;
						overflow: hidden;
					}

					.country-count-diff {
						grid-column: 1/2;
						grid-row: 2/3;
						font-size: 0.8em;
						&.extra {
							color: red;
						}

						&.less {
							color: green;
						}
					}

					.country-count {
						grid-column: 2/3;
						grid-row: 1/3;
						text-align: right;
						font-size: 1.2em;
						font-weight: bold;
					}
				}
			}
		}
	}

	@keyframes loader-bar {
		0% {
			background-position: 0 0;
		}
		100% {
			background-position: 15px 15px;
		}
	}
}

.flag {
	position: relative;
	display: inline-flex;
	width: 2.1em;
	&.fa-globe {
		height: 43px;
		background-color: #fff;
		display: flex;
		justify-content: center;
		align-items: center;
		border-radius: 4px;
		overflow: hidden;
		width: 58px;
		font-size: 0.8em;
	}
	img {
		max-width: 100%;
	}
	&::after {
		content: '';
		width: 100%;
		height: 100%;
		position: absolute;
		display: block;
		mix-blend-mode: overlay;
		box-sizing: border-box;
		//	background-image: linear-gradient(0deg, rgba(0, 0, 0, 0.3) 2%, rgba(255, 255, 255, 0.7) 100%);
		background-image: linear-gradient(0deg, rgba(0, 0, 0, 0.3) 2%, rgba(255, 255, 255, 0.7) 100%);
	}
}

#chartjs-tooltip {
	background: hsl(var(--compl-hue), 73%, 90%);
	filter: drop-shadow(0px 0px 1px hsl(var(--compl-hue), 100%, 30%));
	transform: translate(-50%, -25px);
	z-index: 50;
	padding: 1em;
	pointer-events: none;
	border-radius: 0.2em;
	position: absolute;
	top: 0;
	font-size: 0.9em;
	text-align: left;
	min-width: 150px;
	opacity: 0;

	.title {
		white-space: nowrap;
		font-weight: bold;
	}
	.count-wrapper {
		white-space: nowrap;
		font-size: 0.9em;
		text-transform: capitalize;
		.count-total-type {
			white-space: nowrap;
		}

		.count-total-number {
			padding: 0.2em;
		}
	}
	&::after {
		position: absolute;
		top: 100%;
		left: 50%;
		background-color: hsl(var(--compl-hue), 73%, 90%);
		width: 1em;
		height: 1em;
		transform: translate(-50%, -70%) rotate(45deg);
		content: '';
	}
}
.series-data-wrapper {
	position: relative;
	width: 100%;
	height: 100%;
	display: flex;
	flex-direction: column;
	.series-data-options {
		font-size: 0.8em;
		width: 100%;
		padding: 1em 2em;
		text-align: left;
		transition: $transition;
		display: flex;
		> div {
			display: flex;
			margin: 0 1em;
			border-radius: 0.2em;
			overflow: hidden;
			span {
				padding: 0.3em 0.9em;
				background-color: hsl(var(--compl-hue), 73%, 30%);
				transition: all 0.3s;
				display: inline-block;
				color: hsl(var(--compl-hue), 73%, 75%);
				&:not(.active) {
					cursor: pointer;
					&:hover {
						background-color: hsl(var(--compl-hue), 73%, 20%);
					}
				}
				&.active {
					background-color: hsl(var(--compl-hue), 73%, 15%);
					color: white;
				}
			}
		}
	}
	.series-data {
		width: 100%;
		flex-grow: 1;
		position: relative;
		height: 80%;
	}
}

.loader-wrapper {
	position: absolute;
	top: 50%;
	left: 50%;
	transform: translate(-50%, -50%);
	width: 80%;
	z-index: 50;
	max-width: 1000px;
	.loader-bar {
		width: 100%;
		height: 3px;
		position: relative;
		box-shadow: 0 0 2em 0.5em rgba(103, 103, 103, 0.3);
		&.indeterminant {
			.loader-bar-loaded {
				width: 100%;
				animation: loader-bar 1s linear infinite;
				background-size: 15px 15px;
				background-image: linear-gradient(
					-45deg,
					rgba(255, 255, 255, 0.5) 25%,
					transparent 25% 50%,
					rgba(255, 255, 255, 0.5) 50% 75%,
					transparent 75% 100%
				);
			}

			@keyframes loader-bar {
				0% {
					background-position: 0 0;
				}
				100% {
					background-position: 15px 15px;
				}
			}
		}
		.loader-bar-loaded {
			position: absolute;
			top: 0;
			left: 0;
			height: 100%;
			background-color: hsl(var(--current-hue), 50%, 50%);
			transition: all 0.3s;
		}
	}

	.loader-text {
		text-align: center;
		font-size: 1em;
		margin-top: 1em;
	}
}
#location-tooltip {
	display: block;
	z-index: 50;
	transition: opacity 0.1s linear;
	pointer-events: none;
	min-width: 200px;
	left: 0;
	top: 0;
	position: fixed;
	transform: translate(-50%, 20%);
	font-size: 0.9em;
	.tooltip-inner {
		background: hsl(var(--current-hue), 30%, 20%);
		color: white;
		border-radius: 3px;
		padding: 1em;
		text-align: left;
		.tooltip-title {
			border-bottom: 1px solid white;
			margin-bottom: 1em;
			padding-bottom: 0.3em;
		}

		.tooltip-content {
			font-size: 0.8em;
			margin-top: 1em;
			> div {
				display: flex;
				span {
					width: 50%;
					white-space: nowrap;
					padding: 0.2em 0.2em;
					&:first-child {
						text-align: right;
					}
					&:last-child {
						text-transform: capitalize;
					}
				}
			}
		}
	}
}
.map-container {
	position: absolute;
	top: 50%;
	left: 50%;
	width: 100%;
	max-width: 1000px;
	transform: translate(-50%, -60%) perspective(1000px) rotateX(var(--map-rotate-x))
		rotateY(var(--map-rotate-y));
	max-height: 100%;
	padding: 2em 2em;
	box-sizing: initial;
	background-color: white;
	box-shadow: 0 0 1em 1em hsla(var(--current-hue), 100%, 10%, 0.1);

	.svg-wrapper {
		max-width: 100%;
		max-height: 100%;
		position: relative;
		top: 50%;
		left: 50%;
	}
	svg {
		max-width: 100%;
		max-height: 100%;
		position: relative;
		filter: drop-shadow(0px 0px 2em hsla(var(--current-hue), 100%, 10%, 0.5));
		width: 1000px;
		height: 400px;
		transition: all 0.2s;
		&.loading {
			opacity: 0.5;
		}
	}

	g {
		transition: $transition-ease;
		cursor: pointer;

		&.no-select {
			cursor: default;
		}
		&.active {
			path {
				stroke-width: 0;
				stroke: purple;
				z-index: 5;
			}
		}
	}

	&:not([data-status='single-location']):hover {
		g {
			fill-opacity: 0.5;

			&:hover {
				fill-opacity: 1;
			}
		}
	}

	&[data-status='single-location'] {
		g {
			fill-opacity: 0.1;
			&:hover {
				fill-opacity: 0.3;
			}
			&[data-status='selected'] {
				fill-opacity: 1;
			}
		}
	}
}
View Compiled
const countriesIso = {
	AF: 'Afghanistan',
	AX: 'Åland Islands',
	AL: 'Albania',
	DZ: 'Algeria',
	AS: 'American Samoa',
	AD: 'Andorra',
	AO: 'Angola',
	AI: 'Anguilla',
	AQ: 'Antarctica',
	AG: 'Antigua and Barbuda',
	AR: 'Argentina',
	AM: 'Armenia',
	AW: 'Aruba',
	AU: 'Australia',
	AT: 'Austria',
	AZ: 'Azerbaijan',
	BS: 'Bahamas',
	BH: 'Bahrain',
	BD: 'Bangladesh',
	BB: 'Barbados',
	BY: 'Belarus',
	BE: 'Belgium',
	BZ: 'Belize',
	BJ: 'Benin',
	BM: 'Bermuda',
	BT: 'Bhutan',
	BO: 'Bolivia',
	BA: 'Bosnia and Herzegovina',
	BW: 'Botswana',
	BL: 'Saint Barthélemy',
	BV: 'Bouvet Island',
	BR: 'Brazil',
	IO: 'British Indian Ocean Territory',
	BN: 'Brunei Darussalam',
	BQ: 'BONAIRE, SINT EUSTATIUS AND SABA',
	BG: 'Bulgaria',
	BF: 'Burkina Faso',
	BI: 'Burundi',
	KH: 'Cambodia',
	CM: 'Cameroon',
	CA: 'Canada',
	CV: 'Cape Verde',
	CW: 'Curaçao',
	MF: 'Saint Martin',
	KY: 'Cayman Islands',
	CF: 'Central African Republic',
	TD: 'Chad',
	CL: 'Chile',
	CN: 'China',
	CX: 'Christmas Island',
	CC: 'Cocos (Keeling) Islands',
	CO: 'Colombia',
	KM: 'Comoros',
	CG: 'Congo',
	CD: 'The Democratic Republic of the Congo',
	CK: 'Cook Islands',
	CR: 'Costa Rica',
	CI: "Cote D'Ivoire",
	HR: 'Croatia',
	CU: 'Cuba',
	CY: 'Cyprus',
	CZ: 'Czech Republic',
	DK: 'Denmark',
	DJ: 'Djibouti',
	DM: 'Dominica',
	DO: 'Dominican Republic',
	EC: 'Ecuador',
	EG: 'Egypt',
	SV: 'El Salvador',
	GQ: 'Equatorial Guinea',
	ER: 'Eritrea',
	EE: 'Estonia',
	ET: 'Ethiopia',
	FK: 'Falkland Islands (Malvinas)',
	FO: 'Faroe Islands',
	FJ: 'Fiji',
	FI: 'Finland',
	FR: 'France',
	GF: 'French Guiana',
	PF: 'French Polynesia',
	TF: 'French Southern Territories',
	GA: 'Gabon',
	GM: 'Gambia',
	GE: 'Georgia',
	DE: 'Germany',
	GH: 'Ghana',
	GI: 'Gibraltar',
	GR: 'Greece',
	GL: 'Greenland',
	GD: 'Grenada',
	GP: 'Guadeloupe',
	GU: 'Guam',
	GT: 'Guatemala',
	GG: 'Guernsey',
	GN: 'Guinea',
	GW: 'Guinea-Bissau',
	GY: 'Guyana',
	HT: 'Haiti',
	HM: 'Heard Island and Mcdonald Islands',
	VA: 'Holy See (Vatican City State)',
	HN: 'Honduras',
	HK: 'Hong Kong',
	HU: 'Hungary',
	IS: 'Iceland',
	IN: 'India',
	ID: 'Indonesia',
	IR: 'Islamic Republic Of Iran',
	IQ: 'Iraq',
	IE: 'Ireland',
	IM: 'Isle of Man',
	IL: 'Israel',
	IT: 'Italy',
	JM: 'Jamaica',
	JP: 'Japan',
	JE: 'Jersey',
	JO: 'Jordan',
	KZ: 'Kazakhstan',
	KE: 'Kenya',
	KI: 'Kiribati',
	KP: "Democratic People's Republic of Korea",
	KR: 'Republic of Korea',
	XK: 'Kosovo',
	KW: 'Kuwait',
	KG: 'Kyrgyzstan',
	LA: "Lao People's Democratic Republic",
	LV: 'Latvia',
	LB: 'Lebanon',
	LS: 'Lesotho',
	LR: 'Liberia',
	LY: 'Libyan Arab Jamahiriya',
	LI: 'Liechtenstein',
	LT: 'Lithuania',
	LU: 'Luxembourg',
	MO: 'Macao',
	MK: 'The Former Yugoslav Republic of Macedonia',
	MG: 'Madagascar',
	MW: 'Malawi',
	MY: 'Malaysia',
	MV: 'Maldives',
	ML: 'Mali',
	MT: 'Malta',
	MH: 'Marshall Islands',
	MQ: 'Martinique',
	MR: 'Mauritania',
	MU: 'Mauritius',
	YT: 'Mayotte',
	MX: 'Mexico',
	FM: 'Federated States of Micronesia',
	MD: 'Republic of Moldova',
	MC: 'Monaco',
	MN: 'Mongolia',
	ME: 'Montenegro',
	MS: 'Montserrat',
	MA: 'Morocco',
	MZ: 'Mozambique',
	MM: 'Myanmar',
	NA: 'Namibia',
	NR: 'Nauru',
	NP: 'Nepal',
	NL: 'Netherlands',
	AN: 'Netherlands Antilles',
	NC: 'New Caledonia',
	NZ: 'New Zealand',
	NI: 'Nicaragua',
	NE: 'Niger',
	NG: 'Nigeria',
	NU: 'Niue',
	NF: 'Norfolk Island',
	MP: 'Northern Mariana Islands',
	NO: 'Norway',
	OM: 'Oman',
	PK: 'Pakistan',
	PW: 'Palau',
	PS: 'Occupied Palestinian Territory',
	PA: 'Panama',
	PG: 'Papua New Guinea',
	PY: 'Paraguay',
	PE: 'Peru',
	PH: 'Philippines',
	PN: 'Pitcairn',
	PL: 'Poland',
	PT: 'Portugal',
	PR: 'Puerto Rico',
	QA: 'Qatar',
	RE: 'Reunion',
	RO: 'Romania',
	RU: 'Russian Federation',
	RW: 'Rwanda',
	SH: 'Saint Helena',
	KN: 'Saint Kitts and Nevis',
	LC: 'Saint Lucia',
	PM: 'Saint Pierre and Miquelon',
	VC: 'Saint Vincent and the Grenadines',
	WS: 'Samoa',
	SM: 'San Marino',
	ST: 'Sao Tome and Principe',
	SA: 'Saudi Arabia',
	SN: 'Senegal',
	RS: 'Serbia',
	SC: 'Seychelles',
	SL: 'Sierra Leone',
	SG: 'Singapore',
	SK: 'Slovakia',
	SI: 'Slovenia',
	SS: 'South Sudan',
	SB: 'Solomon Islands',
	SO: 'Somalia',
	ZA: 'South Africa',
	GS: 'South Georgia and the South Sandwich Islands',
	ES: 'Spain',
	LK: 'Sri Lanka',
	SD: 'Sudan',
	SR: 'Suriname',
	SJ: 'Svalbard and Jan Mayen',
	SZ: 'Swaziland',
	SE: 'Sweden',
	CH: 'Switzerland',
	SY: 'Syrian Arab Republic',
	TW: 'Taiwan',
	TJ: 'Tajikistan',
	TZ: 'United Republic of Tanzania',
	TH: 'Thailand',
	TL: 'Timor-Leste',
	TG: 'Togo',
	TK: 'Tokelau',
	TO: 'Tonga',
	TT: 'Trinidad and Tobago',
	TN: 'Tunisia',
	TR: 'Turkey',
	TM: 'Turkmenistan',
	TC: 'Turks and Caicos Islands',
	TV: 'Tuvalu',
	UG: 'Uganda',
	UA: 'Ukraine',
	SX: 'Sint Maarten',
	AE: 'United Arab Emirates',
	GB: 'United Kingdom',
	US: 'United States',
	UM: 'United States Minor Outlying Islands',
	UY: 'Uruguay',
	UZ: 'Uzbekistan',
	VU: 'Vanuatu',
	VE: 'Venezuela',
	VN: 'Viet Nam',
	VG: 'British Virgin Islands',
	VI: 'U.S. Virgin Islands',
	WF: 'Wallis and Futuna',
	EH: 'Western Sahara',
	YE: 'Yemen',
	ZM: 'Zambia',
	ZW: 'Zimbabwe',
	WO: 'World'
};
const store = new Vuex.Store({
	state: {
		day_cases: {
			by_locations: {
				confirmed: [],
				recovered: [],
				deaths: []
			},
			max: {
				confirmed: 0,
				recovered: 0,
				deaths: 0
			}
		},
		displayed_series: {
			aggregate: {
				confirmed: [],
				recovered: [],
				deaths: []
			},
			by_locations: {
				confirmed: [],
				recovered: [],
				deaths: []
			},
			max: {
				confirmed: 0,
				recovered: 0,
				deaths: 0
			},
			total: {
				confirmed: 0,
				recovered: 0,
				deaths: 0
			}
		},
		all_series: {},
		cases_type: 'confirmed',
		current_location: '',
		worldmap: {},
		current_hover_location: '',
		notify: {
			show: false,
			message: '',
			type: 'error'
		},
		location_loading: false
	},
	mutations: {
		update_location_loading(state, status) {
			state.location_loading = status;
		},
		update_day_cases(state, payload) {
			const cases = payload.data;
			// Get max of each data series
			state.day_cases = cases;
		},
		update_cases_type(state, type) {
			state.cases_type = type;
			// Set current hue to modify colors
			const hue = getComputedStyle(document.documentElement).getPropertyValue(`--${type}-hue`);
			document.documentElement.style.setProperty('--current-hue', hue);
		},
		update_displayed_series(state, data) {
			state.series = data;
		},
		update_all_series(state, payload) {
			const { data, extra } = payload;
			const { iso } = extra;
			state.all_series[iso] = data;
		},
		update_location(state, location) {
			state.current_location = location;
		},
		update_world_map(state, payload) {
			const map = payload.data;
			state.worldmap = JSON.parse(map);
		},
		update_current_hover_location(state, iso) {
			state.current_hover_location = iso;
		},
		update_notify(state, data) {
			state.notify = { ...state.notify, ...data };
		}
	},
	getters: {
		getLocationLoading(state) {
			return state.location_loading;
		},
		getNotifyDetails(state) {
			return state.notify;
		},
		getCountriesList() {
			return countriesIso;
		},
		getCurrentHoverLocation(state) {
			return state.current_hover_location;
		},
		getWorldMap(state) {
			return state.worldmap;
		},
		/**
		 * Get world map path and value for each location
		 *
		 * @param {object} state
		 * @param {object} getters
		 */
		getWorldMapData(state, getters) {
			const cases = getters.getDayCaseByType;
			const mapData = Object.keys(getters.getWorldMap).reduce((acc, key) => {
				const value = cases[key]?.value || 0;
				acc[key] = {
					path: getters.getWorldMap[key],
					value
				};
				return acc;
			}, {});

			return mapData.length === 0 ? [] : mapData;
		},
		/**
		 * Retrun cases based on selected type ('confirmed', 'recovered', 'deaths')
		 *
		 * @param {object} state
		 */
		getDayCaseByType(state) {
			const cases = state.day_cases.by_locations[state.cases_type];

			const keys = Object.keys(cases).sort((a, b) => cases[b].value - cases[a].value);
			const casesOrdered = {};
			keys.forEach(k => (casesOrdered[k] = cases[k]));
			return casesOrdered.length === 0 ? {} : casesOrdered;
		},
		/**
		 * Get total day cases count
		 *
		 * @param {object} state
		 */
		getDayCasesTotalCount(state) {
			const casesTypes = ['confirmed', 'deaths', 'recovered'];
			const casesTotal = {};
			casesTypes.forEach(caseType => {
				casesTotal[caseType] =
					state.day_cases.by_locations[caseType][state.current_location]?.value;
			});
			return casesTotal;
		},
		getMaxCountPerDay(state) {
			const max = state.day_cases.max[state.cases_type];
			return max || 0;
		},
		getDisplayedCasesType(state) {
			return state.cases_type;
		},
		getAllSeries(state) {
			return state.all_series;
		},
		getDisplayedSeries(state) {
			return state.all_series[state.current_location];
		},
		getSeriesByCaseType(state, getters) {
			return getters.getDisplayedSeries?.aggregate[state.cases_type] || [];
		},
		getCurrentLocation(state) {
			return state.current_location;
		}
	},
	actions: {
		loadFromBackend({ commit }, { endpoint, request_data = {}, config = {}, mutation_key }) {
			return new Promise((resolve, reject) => {
				this._vm.$http
					.post(endpoint, request_data, config)
					.then(response => {
						commit(mutation_key, { data: response.data, extra: request_data });
						resolve(response.data);
					})
					.catch(error => {
						commit('update_notify', { message: error, show: true });
						reject(error.response);
					});
			});
		}
	}
});
const { mapGetters, mapMutations } = Vuex;

let axi = axios.create({
	baseURL: 'https://covid.kalimah-apps.com/wp-json/api/v1/',
	// baseURL: 'http://kitab.test/wp-json/api/v1/',
	timeout: 100000
});
Vue.prototype.$http = axi;
Vue.component('loaderBar', {
	template: '#loader-bar-template',
	props: ['loading', 'loadingText', 'loadedPercentage', 'loadingIndeterminant']
});

Vue.component('countrieslist', {
	template: '#countries-list-template',
	data() {
		return {
			search_keywords: '',
			is_loading: {}
		};
	},
	computed: {
		...mapGetters([
			'getDayCaseByType',
			'getCountriesList',
			'getCurrentLocation',
			'getAllSeries',
			'getLocationLoading'
		]),
		getCasesWithCountriesNames() {
			return Object.keys(this.getDayCaseByType).reduce((list, iso) => {
				list[iso] = this.getDayCaseByType[iso];
				list[iso].name = this.getCountriesList[iso];
				return list;
			}, {});
		},
		filterCountriesList() {
			const locationsList = this.getCasesWithCountriesNames;
			if (this.search_keywords === '') return locationsList;

			return Object.keys(locationsList).reduce((list, iso) => {
				const location = locationsList[iso];
				if (iso !== 'none' && location.name.toLowerCase().indexOf(this.search_keywords) > -1) {
					list[iso] = locationsList[iso];
				}
				return list;
			}, {});
		}
	},
	methods: {
		...mapMutations(['update_location', 'update_location_loading']),
		selectCountry(iso) {
			// check if the data for this location has been loaded already
			if (iso in this.getAllSeries) {
				this.update_location(iso);
				return;
			}

			// Don't process if there is another country data being loaded
			if (this.getLocationLoading !== false) return;

			// show loader
			//this.$set(this.is_loading, iso, true);
			this.update_location_loading(iso);

			this.$store
				.dispatch('loadFromBackend', {
					endpoint: 'get-total-series',
					request_data: { iso },
					mutation_key: 'update_all_series'
				})
				.then(() => {
					this.update_location(iso);
					this.update_location_loading(false);
				})
				.catch(() => {
					this.update_location_loading(false);
				});
		},
		filterCountries(e) {
			this.search_keywords = e.target.value.toLowerCase();
		}
	}
});

Vue.component('flag', {
	template: '#flag-template',
	props: ['iso'],
	computed: {
		flag() {
			if (this.iso === 'none' || this.iso === '') return '';
			//return `https://covid.kalimah-apps.com/index.php?flag_iso=${this.iso}`;
			return `https://raw.githubusercontent.com/lipis/flag-icon-css/master/flags/4x3/${this.iso}.svg`;
		}
	}
});

Vue.component('location-tooltip', {
	template: '#location-tooltip-template',
	data() {
		return {
			tooltip_x: 0,
			tooltip_y: 0,
			show_tooltip: false
		};
	},
	mounted() {
		this.$root.$on('mouse_coord', coord => {
			this.tooltip_x = coord.tooltip_x;
			this.tooltip_y = coord.tooltip_y;
		});

		this.$root
			.$on('tooltip_on', () => {
				this.show_tooltip = true;
			})
			.$on('tooltip_off', () => {
				this.show_tooltip = false;
			});
	},
	computed: {
		...mapGetters([
			'getDayCaseByType',
			'getCountriesList',
			'getCurrentHoverLocation',
			'getDisplayedCasesType'
		]),
		getTooltipData() {
			const location = this.getCurrentHoverLocation;

			if (location === 'WO' || location === '') return {};

			const data = this.getDayCaseByType[location];
			// Some countires (Turkminstan and Greenland) don't have data
			if (typeof data === 'undefined') {
				return {
					title: this.getCountriesList[location]
				};
			}

			// Get data and return an object
			const dailyDiffValue = data.diff.value;
			const dailyDiffType = data.diff.type;

			// Make sure percentage is only showing two decimal points
			const dailyPercentage = (data.diff.value * 100) / data.value;
			const dailyPercentageRounded = Math.round(dailyPercentage * 100) / 100;

			return {
				title: this.getCountriesList[location],
				type: this.getDisplayedCasesType,
				count: data.value.toLocaleString('en-US'),
				diff: dailyDiffValue.toLocaleString('en-US'),
				difftext: dailyDiffType === 'extra' ? 'Increase' : 'Decrease',
				percentage: dailyPercentageRounded
			};
		},
		getTooltipStyle() {
			const left = this.tooltip_x;
			const top = this.tooltip_y;

			return {
				opacity: this.show_tooltip === true ? 1 : 0,
				left: `${left}px`,
				top: `${top}px`
			};
		}
	}
});

Vue.component('notify', {
	template: '#notify-template',
	props: ['show', 'type', 'message'],
	data() {
		return {
			show_notify: false
		};
	},
	methods: {
		...mapMutations(['update_notify'])
	},
	watch: {
		show(newShowValue) {
			this.show_notify = newShowValue;
			if (newShowValue === false) return;
			setTimeout(() => {
				this.update_notify({ message: '', show: false });
			}, 8000);
		}
	}
});

Vue.component('worldmap', {
	template: '#worldmap-template',
	data() {
		return {
			map_container: null,
			map_svg: null,
			loading: true,
			loading_text: 'Loading Map',
			loaded_percentage: 0,
			viewbox: '0 267 1000 400',
			show_tooltip_timer: null,
			show_tooltip: false,
			tooltip_x: 0,
			tooltip_y: 0,
			x_rotate: 0,
			y_rotate: 0
		};
	},
	mounted() {
		this.$store.dispatch('loadFromBackend', {
			endpoint: 'get-day-cases',
			mutation_key: 'update_day_cases'
		});
		// Load world map on start
		this.$store
			.dispatch('loadFromBackend', {
				endpoint: 'get-world-map',
				mutation_key: 'update_world_map',
				config: {
					onDownloadProgress: progressEvent => {
						const total = parseFloat(
							progressEvent.currentTarget.getResponseHeader('x-map-filesize')
						);
						const current = progressEvent.loaded;

						const percentCompleted = Math.floor((current / total) * 100);
						this.loaded_percentage = percentCompleted;
					}
				}
			})
			.then(() => {
				this.loading = false;
			})
			.catch(() => {
				this.loading = false;
			});

		this.map_container = this.$refs.world_map_container;
		this.map_svg = this.$refs.world_map;
	},
	methods: {
		...mapMutations([
			'update_location',
			'update_current_hover_location',
			'update_location_loading'
		]),
		resetLocation() {
			this.update_location('WO');
		},
		/**
		 * Rotate map on mouse move
		 *
		 * @param {object} e Event
		 */
		rotateMap(e) {
			const rect = this.map_svg.getBoundingClientRect();
			const left = e.x - rect.x;
			const top = e.y - rect.y;
			const horizontalMiddle = rect.height / 2;
			const verticalMiddle = rect.width / 2;
			this.x_rotate = (((top - horizontalMiddle) * 4) / horizontalMiddle) * -1;
			this.y_rotate = ((left - verticalMiddle) * 4) / verticalMiddle;
		},
		stopRotateMap() {
			setTimeout(() => {
				this.x_rotate = 0;
				this.y_rotate = 0;
			}, 200);
		},
		selectLocation(iso) {
			// Dont process click if not data available
			const data = this.getDayCaseByType[iso];
			if (typeof data === 'undefined') {
				return;
			}

			// check if the data for this location has been loaded already
			if (iso in this.getAllSeries) {
				this.update_location(iso);
				return;
			}

			// show loader
			this.update_location_loading(true);

			this.$store
				.dispatch('loadFromBackend', {
					endpoint: 'get-total-series',
					request_data: { iso },
					mutation_key: 'update_all_series'
				})
				.then(() => {
					this.update_location(iso);
					this.update_location_loading(false);
				})
				.catch(() => {
					this.update_location_loading(false);
				});
		},
		/**
		 * Each additional decimal place means differnt range of lightness.
		 * 100, 1000, 10000 and so on.
		 * This function calculate the lightness within the range of the decimal places
		 */
		getLightRange(max, range) {
			const split = max.toString().split('');
			const l = split.length - 1;
			return {
				max: Math.round((50 / l) * range),
				min: Math.round((50 / l) * range - 1)
			};
		},
		/**
		 * Get percentage number within a provided range
		 */
		getPercentageRangeNumber(min, max, percent) {
			return Math.round(min + ((max - min) * percent) / 50);
		},
		/**
		 * Get color of location based on its value
		 * Darker means higher value
		 */
		getColor(value) {
			// Get decimal places
			const decimalPlaces = value.toString().split('').length;
			const maxCount = this.getMaxCountPerDay;

			// Get lightness percentage between 0 and 50
			const lightnessPercent = Math.round((value * 50) / maxCount);
			const range = this.getLightRange(maxCount, decimalPlaces);
			const adjustedLightnessPercentage = this.getPercentageRangeNumber(
				range.min,
				range.max,
				lightnessPercent
			);

			const hueColor = `${this.getCurrentHueColor()}deg`;
			/*
			 * return hsl color. 80  is used to reverse colors
			 * to represent higher values with dark color accents and
			 * lower values with light color accents
			 */
			return `hsl(${hueColor}, 70%, ${80 - adjustedLightnessPercentage}%)`;
		},
		/**
		 * Handle mouse enter on specific location to show tooltip
		 *
		 * @param {object} event
		 * @param {string} iso
		 */
		handleMoustEnter(event, iso) {
			clearTimeout(this.show_tooltip_timer);
			this.update_current_hover_location(iso);
			this.$root.$emit('tooltip_on');
		},
		/**
		 * Emit coordinates on mouse move to move tooltip
		 *
		 * @param {object} e Mouse event object
		 */
		handleMouseMove(e) {
			this.$root.$emit('mouse_coord', { tooltip_x: e.pageX, tooltip_y: e.pageY });
		},
		/**
		 * Hide tooltip on mouse out
		 */
		handleMoustOut() {
			clearTimeout(this.show_tooltip_timer);
			this.show_tooltip_timer = setTimeout(() => {
				// this.show_tooltip = false;
				this.$root.$emit('tooltip_off');
			}, 100);
		},

		getCurrentHueColor() {
			return getComputedStyle(document.documentElement).getPropertyValue('--current-hue');
		}
	},
	computed: {
		...mapGetters([
			'getDayCaseByType',
			'getWorldMapData',
			'getMaxCountPerDay',
			'getDisplayedCasesType',
			'getCountriesList',
			'getCurrentLocation',
			'getAllSeries',
			'getCurrentHoverLocation',
			'getLocationLoading'
		]),

		getMapStyle() {
			return {
				'--map-rotate-x': `${this.x_rotate}deg`,
				'--map-rotate-y': `${this.y_rotate}deg`
			};
		},
		/**
		 * Return world map data with choropleth hsl values
		 */
		getWorlMapWithColors() {
			return Object.keys(this.getWorldMapData).reduce((acc, key) => {
				acc[key] = this.getWorldMapData[key];
				acc[key].color = this.getColor(acc[key].value);
				return acc;
			}, {});
		},
		getMaxCount() {
			return Object.keys(this.getWorldMapData);
		}
	},
	watch: {
		getWorlMapWithColors() {
			this.$nextTick(() => {});
		},
		getCurrentLocation(newLocationsIso) {
			const iso = newLocationsIso;
			// let viewbox = '0 0 700.9375 337.375';
			let viewbox = '0 267.77886962890625 1000 400';

			// get locations iso (if world is not selected)
			if (iso !== 'WO' && this.map_svg != null) {
				const { width, height, x, y } = document.querySelector(`#${iso}`).getBBox();
				const halfWidth = width / 2;
				const halfHeight = height / 2;
				viewbox = `${x - halfWidth} ${y - halfHeight} ${width + width} ${height + height}`;
			}
			// animate viewbox for selected ISO
			TweenLite.to(this.$data, {
				duration: 1.5,
				ease: 'expo.out',
				viewbox: viewbox
			});
		}
	}
});

Vue.component('series', {
	template: '#series-template',
	data() {
		return {
			chart: null,
			chart_type: 'bar',
			series_type: 'daily',
			series_representation: 'change',
			series_loading: true,
			monthNames: [
				'Jan',
				'Feb',
				'Mar',
				'Apr',
				'May',
				'Jun',
				'Jul',
				'Aug',
				'Sep',
				'Oct',
				'Nov',
				'Dec'
			]
		};
	},
	mounted() {
		// Check if there is data in localStorage and apply it
		const keys = ['chart_type', 'series_type', 'series_representation'];
		keys.forEach(k => {
			const stateValue = localStorage.getItem(k);
			if (stateValue != null) this[k] = stateValue;
		});
		/**
		 * Show a dashed line vertically on line graphs
		 */
		Chart.plugins.register({
			afterDatasetDraw: (chart, dataset) => {
				if (chart.tooltip._active && chart.tooltip._active.length && dataset.meta.type === 'line') {
					const activePoint = chart.tooltip._active[0];
					const { ctx } = chart;
					const yAxis = chart.scales['y-axis-0'];
					const { x } = activePoint.tooltipPosition();
					const topY = yAxis.top;
					const bottomY = yAxis.bottom; // activePoint.tooltipPosition().y;

					// Get current hue and darken it
					const currentHue = this.getCurrentHueColor();

					// draw line
					ctx.save();
					ctx.beginPath();
					ctx.setLineDash([3, 3]);
					ctx.moveTo(x, topY);
					ctx.lineTo(x, bottomY);
					ctx.lineWidth = 1;
					ctx.strokeStyle = `hsla(${currentHue}deg, 100%, 15%, 0.8)`;
					ctx.stroke();
					ctx.restore();
				}
			}
		});

		// Setup chart
		Chart.defaults.scale.gridLines.drawOnChartArea = false;
		this.chart = new Chart(this.$refs.chart, {
			type: 'bar',
			data: {
				labels: [],
				datasets: [
					{
						label: 'Count',
						backgroundColor: '#3e95cd',
						hoverBackgroundColor: 'blue'
					},
					{
						label: 'Count',
						fill: false,
						type: 'line',
						pointRadius: 0,
						pointHoverRadius: 6,
						pointHoverBackgroundColor: 'red',
						pointHoverBorderWidth: 0,
						hidden: true
					}
				]
			},
			options: {
				hover: {
					mode: 'index',
					intersect: false,
					animationDuration: 0
				},
				tooltips: {
					intersect: false,
					enabled: false,
					mode: 'index',
					/**
					 * Custom tooltip with div
					 */
					custom(tooltipModel) {
						// Tooltip Element
						const tooltipEl = document.querySelector('#chartjs-tooltip');
						const title = tooltipEl.querySelector('.title');
						const body = tooltipEl.querySelector('.count-total-number');

						// Hide/show  tooltip
						tooltipEl.style.opacity = tooltipModel.opacity;

						// Set Text
						if (tooltipModel.body) {
							const titleLines = tooltipModel.title || [];
							const bodyLines = tooltipModel.body.map(bodyItem => bodyItem.lines);
							title.innerHTML = titleLines[0];
							body.innerHTML = bodyLines[0];
						}

						tooltipEl.style.left = `${tooltipModel.caretX}px`;
					},
					callbacks: {
						label: tooltipItem => {
							let label = this.getDisplayedCasesType;

							if (label) {
								label += ': ';
							}
							label += tooltipItem.yLabel.toLocaleString('en-US');
							return label;
						}
					}
				},
				maintainAspectRatio: false,
				legend: { display: false },
				scales: {
					yAxes: [
						{
							ticks: {
								beginAtZero: true,
								callback: value => this.format(value)
							}
						}
					],
					xAxes: [
						{
							type: 'time',
							offset: true
						}
					]
				}
			}
		});

		// check if the data for this location has been loaded already
		if ('WO' in this.getAllSeries) {
			this.update_location('WO');
			this.series_loading = false;
			return;
		}

		this.$store
			.dispatch('loadFromBackend', {
				endpoint: 'get-total-series',
				request_data: { iso: 'WO' },
				mutation_key: 'update_all_series'
			})
			.then(() => {
				this.series_loading = false;
				this.update_location('WO');
				this.updateChart();
			})
			.catch(() => {
				this.series_loading = false;
			});
	},
	methods: {
		...mapMutations(['update_location']),
		/**
		 * Format long numbers to display short numbers with B for billions,
		 * M for millions and K for thousands
		 */
		format(num) {
			const number = Math.abs(Number(num));
			let formattedNumber = number;
			// Nine Zeroes for Billions
			switch (true) {
				case number >= 1.0e9:
					formattedNumber = `${Math.round(number) / 1.0e9} B`;
					break;
				case number >= 1.0e6:
					formattedNumber = `${Math.round(number) / 1.0e6} M`;
					break;
				case number >= 1.0e3:
					formattedNumber = `${Math.round(number) / 1.0e3} K`;
					break;
				default:
					formattedNumber = number;
					break;
			}

			return formattedNumber;
		},
		getCurrentHueColor() {
			return getComputedStyle(document.documentElement).getPropertyValue('--current-hue');
		},
		/**
		 * Update chart to reflect changes. FromTo daily and weekly. FromTo bar and line,
		 * FromTo cumlative, change and log
		 */
		updateChart(enableAnimation = false) {
			const newType = this.series_type;
			const seriesData = this.getSeriesByCaseType;

			if (seriesData.length === 0) return;

			const data = [];
			const labels = [];
			let xAxes = {};
			const rep = this.series_representation;
			// if series type is set week, aggregate data
			if (newType === 'weekly') {
				let lastWeek = 0;
				let count = 0;
				seriesData.forEach(series => {
					const currentWeek = moment(series.date).week();
					count += rep === 'cumulative' ? parseInt(series.value) : parseInt(series.diff.value);
					if (lastWeek !== currentWeek) {
						labels.push(series.date);
						data.push(count);
						count = 0;
						lastWeek = currentWeek;
					}
				});

				xAxes = {
					barPercentage: 1.1,
					categoryPercentage: 1.2,
					time: {
						unit: 'week',
						unitStepSize: 5,
						displayFormats: { week: 'w - YYYY' }
					}
				};
			} else {
				seriesData.forEach(series => {
					data.push(rep === 'cumulative' ? series.value : series.diff.value);
					labels.push(series.date);
				});
				xAxes = {
					barPercentage: 0.8,
					categoryPercentage: 0.9,
					time: {
						unit: 'month',
						unitStepSize: 1,
						displayFormats: { month: 'MMM YYYY' }
					}
				};
			}

			// update x axes
			this.chart.options.scales.xAxes[0] = { ...this.chart.options.scales.xAxes[0], ...xAxes };

			// Show logarthmic or linear scale
			let yAxesType = 'linear';
			if (this.series_representation === 'log') yAxesType = 'logarithmic';
			this.chart.options.scales.yAxes[0].type = yAxesType;

			// update dataset
			this.chart.data.datasets.forEach((dataset, i) => {
				dataset.data = data;
				// updte color baased on series selected
				dataset.backgroundColor = `hsl(${this.getCurrentHueColor()}deg, 80%, 50%)`;
				dataset.borderColor = `hsl(${this.getCurrentHueColor()}deg, 80%, 50%)`;

				// Bar hover color
				dataset.hoverBackgroundColor = `hsl(${this.getCurrentHueColor()}deg, 100%, 10%)`;

				// line point (circle) background color and border color
				dataset.pointHoverBackgroundColor = `hsl(${this.getCurrentHueColor()}deg, 80%, 50%)`;
				dataset.pointHoverBorderColor = `hsl(${this.getCurrentHueColor()}deg, 80%, 50%)`;

				// Hide/show dataset based on chart type
				if (this.chart_type === 'bar') {
					dataset.hidden = i !== 0;
				} else {
					dataset.hidden = i !== 1;
				}
			});
			this.chart.data.labels = labels;
			if (!enableAnimation) this.chart.update(0);
			else this.chart.update();
		}
	},
	computed: {
		...mapGetters(['getSeriesByCaseType', 'getDisplayedCasesType', 'getAllSeries'])
	},
	watch: {
		chart_type(newType) {
			localStorage.setItem('chart_type', newType);
			this.updateChart(true);
		},
		series_type(newType) {
			localStorage.setItem('series_type', newType);
			this.updateChart();
		},
		series_representation(newRepresentation) {
			localStorage.setItem('series_representation', newRepresentation);
			this.updateChart(true);
		},
		getDisplayedCasesType() {
			// update chart
			this.updateChart(true);
		},
		getSeriesByCaseType() {
			this.updateChart(true);
		}
	}
});
new Vue({
	el: '#app',
	store,
	data: {
		show_countries_list: false,
		confirmed: 0,
		recovered: 0,
		deaths: 0,
		show_copyright: false,
		is_full_screen: false,
		copyright_data: {
			'Covid Data': 'https://github.com/CSSEGISandData/COVID-19',
			Flags: 'https://github.com/lipis/flag-icon-css',
			Map: 'https://github.com/markmarkoh/datamaps',
			FontAwesome: 'https://fontawesome.com/',
			'Chart.js': 'https://www.chartjs.org/',
			gsap: 'https://greensock.com/gsap/',
			'moment.js': 'https://momentjs.com/',
			'Subtle Patterns': 'https://www.toptal.com/designers/subtlepatterns/'
		}
	},
	methods: {
		...mapMutations({ changeCaseType: 'update_cases_type' }),
		enterFullScreen() {
			const element = document.querySelector('#app');
			const requestFullScreen =
				element.requestFullscreen ||
				element.webkitRequestFullScreen ||
				element.mozRequestFullScreen ||
				element.msRequestFullScreen;
			requestFullScreen.call(element);
		},
		exitFullScreen() {
			const cancellFullScreen =
				document.exitFullscreen ||
				document.mozCancelFullScreen ||
				document.webkitExitFullscreen ||
				document.msExitFullscreen;
			cancellFullScreen.call(document);
		}
	},
	computed: {
		...mapGetters([
			'getDayCasesTotalCount',
			'getDisplayedCasesType',
			'getCurrentLocation',
			'getCountriesList',
			'getNotifyDetails'
		]),
		recoveredCount() {
			return Number.isNaN(this.recovered) ? 'No Data' : this.recovered.toLocaleString('en-US');
		},
		deathsCount() {
			return this.deaths.toLocaleString('en-US');
		},
		confirmedCount() {
			return this.confirmed.toLocaleString('en-US');
		}
	},
	watch: {
		/**
		 * Animate count change on every update
		 */
		getDayCasesTotalCount(newTotals) {
			Object.keys(newTotals).forEach(caseType => {
				TweenLite.to(this.$data, 1, {
					[caseType]: newTotals[caseType],
					roundProps: caseType,
					ease: 'expo.out'
				});
			});
		},
		is_full_screen(newValue) {
			if (newValue === true) this.enterFullScreen();
			else this.exitFullScreen();
		}
	}
});

External CSS

  1. https://cdnjs.cloudflare.com/ajax/libs/Chart.js/2.9.4/Chart.min.css

External JavaScript

  1. https://cdnjs.cloudflare.com/ajax/libs/moment.js/2.29.1/moment.min.js
  2. https://cdnjs.cloudflare.com/ajax/libs/Chart.js/2.9.4/Chart.min.js
  3. https://cdnjs.cloudflare.com/ajax/libs/vuex/3.6.0/vuex.min.js
  4. https://cdnjs.cloudflare.com/ajax/libs/gsap/3.6.0/gsap.min.js
  5. https://cdnjs.cloudflare.com/ajax/libs/axios/0.21.1/axios.min.js
  6. https://cdnjs.cloudflare.com/ajax/libs/vue/2.6.11/vue.min.js