<div dir="rtl" style="text-align: right;" trbidi="on">
يعرض هذا النمودج الجدول الزمني لأوبئة COVID-19.
<br />
<!-- Styles -->
<style>
#chartdiv {
max-width: 100%;
height: 800px;
background-color:#212327;
}
</style>
<!-- Resources -->
<script src="https://www.amcharts.com/lib/4/core.js"></script>
<script src="https://www.amcharts.com/lib/4/charts.js"></script>
<script src="https://www.amcharts.com/lib/4/maps.js"></script>
<script src="https://www.amcharts.com/lib/4/geodata/worldLow.js"></script>
<script src="https://covid.amCharts.com/data/js/world_timeline.js"></script>
<script src="https://covid.amCharts.com/data/js/total_timeline.js"></script>
<script src="https://www.amcharts.com/lib/4/themes/animated.js"></script>
<!-- Chart code -->
<script>
am4core.ready(function() {
// Themes begin
am4core.useTheme(am4themes_animated);
// Themes end
var backgroundColor = am4core.color("#1e2128");
var activeColor = am4core.color("#ff8726");
var confirmedColor = am4core.color("#d21a1a");
var recoveredColor = am4core.color("#45d21a");
var deathsColor = am4core.color("#1c5fe5");
// for an easier access by key
var colors = { active: activeColor, confirmed: confirmedColor, recovered: recoveredColor, deaths: deathsColor };
var countryColor = am4core.color("#3b3b3b");
var countryStrokeColor = am4core.color("#000000");
var buttonStrokeColor = am4core.color("#ffffff");
var countryHoverColor = am4core.color("#1b1b1b");
var activeCountryColor = am4core.color("#0f0f0f");
var currentIndex;
var currentCountry = "World";
// last date of the data
var lastDate = new Date(covid_total_timeline[covid_total_timeline.length - 1].date);
var currentDate = lastDate;
var currentPolygon;
var countryDataTimeout;
var currentType;
var sliderAnimation;
//////////////////////////////////////////////////////////////////////////////
// PREPARE DATA
//////////////////////////////////////////////////////////////////////////////
// make a map of country indexes for later use
var countryIndexMap = {};
var list = covid_world_timeline[covid_world_timeline.length - 1].list;
for (var i = 0; i < list.length; i++) {
var country = list[i]
countryIndexMap[country.id] = i;
}
// calculated active cases in world data (active = confirmed - recovered)
for (var i = 0; i < covid_total_timeline.length; i++) {
var di = covid_total_timeline[i];
di.active = di.confirmed - di.recovered;
}
// function that returns current slide
// if index is not set, get last slide
function getSlideData(index) {
if (index == undefined) {
index = covid_world_timeline.length - 1;
}
var data = covid_world_timeline[index];
return data;
}
// get slide data
var slideData = getSlideData();
// as we will be modifying raw data, make a copy
var mapData = JSON.parse(JSON.stringify(slideData.list));
var max = { confirmed: 0, recovered: 0, deaths: 0 };
// the last day will have most
for (var i = 0; i < mapData.length; i++) {
var di = mapData[i];
if (di.confirmed > max.confirmed) {
max.confirmed = di.confirmed;
}
if (di.recovered > max.recovered) {
max.recovered = di.recovered;
}
if (di.deaths > max.deaths) {
max.deaths = di.deaths
}
max.active = max.confirmed;
}
// END OF DATA
//////////////////////////////////////////////////////////////////////////////
// LAYOUT & CHARTS
//////////////////////////////////////////////////////////////////////////////
// main container
// https://www.amcharts.com/docs/v4/concepts/svg-engine/containers/
var container = am4core.create("chartdiv", am4core.Container);
container.width = am4core.percent(100);
container.height = am4core.percent(100);
container.fontSize = "0.9em";
// MAP CHART
// https://www.amcharts.com/docs/v4/chart-types/map/
var mapChart = container.createChild(am4maps.MapChart);
mapChart.height = am4core.percent(80);
mapChart.zoomControl = new am4maps.ZoomControl();
mapChart.zoomControl.align = "right";
mapChart.zoomControl.marginRight = 15;
mapChart.zoomControl.valign = "middle";
// by default minus button zooms out by one step, but we modify the behavior so when user clicks on minus, the map would fully zoom-out and show world data
mapChart.zoomControl.minusButton.events.on("hit", showWorld);
// clicking on a "sea" will also result a full zoom-out
mapChart.seriesContainer.background.events.on("hit", showWorld);
mapChart.seriesContainer.background.events.on("over", resetHover);
mapChart.seriesContainer.background.fillOpacity = 0;
mapChart.zoomEasing = am4core.ease.sinOut;
// https://www.amcharts.com/docs/v4/chart-types/map/#Map_data
// you can use more accurate world map or map of any other country - a wide selection of maps available at: https://github.com/amcharts/amcharts4-geodata
mapChart.geodata = am4geodata_worldLow;
// Set projection
// https://www.amcharts.com/docs/v4/chart-types/map/#Setting_projection
// instead of Miller, you can use Mercator or many other projections available: https://www.amcharts.com/demos/map-using-d3-projections/
mapChart.projection = new am4maps.projections.Miller();
mapChart.panBehavior = "move";
// when map is globe, beackground is made visible
mapChart.backgroundSeries.mapPolygons.template.polygon.fillOpacity = 0.05;
mapChart.backgroundSeries.mapPolygons.template.polygon.fill = am4core.color("#ffffff");
mapChart.backgroundSeries.hidden = true;
// Map polygon series (defines how country areas look and behave)
var polygonSeries = mapChart.series.push(new am4maps.MapPolygonSeries());
polygonSeries.dataFields.id = "id";
polygonSeries.exclude = ["AQ"]; // Antarctica is excluded in non-globe projection
polygonSeries.useGeodata = true;
polygonSeries.nonScalingStroke = true;
polygonSeries.strokeWidth = 0.5;
// this helps to place bubbles in the visual middle of the area
polygonSeries.calculateVisualCenter = true;
var polygonTemplate = polygonSeries.mapPolygons.template;
polygonTemplate.fill = countryColor;
polygonTemplate.fillOpacity = 1
polygonTemplate.stroke = countryStrokeColor;
polygonTemplate.strokeOpacity = 0.15
polygonTemplate.setStateOnChildren = true;
polygonTemplate.events.on("hit", handleCountryHit);
polygonTemplate.events.on("over", handleCountryOver);
polygonTemplate.events.on("out", handleCountryOut);
// you can have pacific - centered map if you set this to -154.8
mapChart.deltaLongitude = -10;
// polygon states
var polygonHoverState = polygonTemplate.states.create("hover");
polygonHoverState.properties.fill = countryHoverColor;
var polygonActiveState = polygonTemplate.states.create("active")
polygonActiveState.properties.fill = activeCountryColor;
// Bubble series
var bubbleSeries = mapChart.series.push(new am4maps.MapImageSeries());
bubbleSeries.data = mapData;
bubbleSeries.dataFields.value = "confirmed";
bubbleSeries.dataFields.id = "id";
// adjust tooltip
bubbleSeries.tooltip.animationDuration = 0;
bubbleSeries.tooltip.showInViewport = false;
bubbleSeries.tooltip.background.fillOpacity = 0.2;
bubbleSeries.tooltip.getStrokeFromObject = true;
bubbleSeries.tooltip.getFillFromObject = false;
bubbleSeries.tooltip.background.fillOpacity = 0.2;
bubbleSeries.tooltip.background.fill = am4core.color("#000000");
var imageTemplate = bubbleSeries.mapImages.template;
// if you want bubbles to become bigger when zoomed, set this to false
imageTemplate.nonScaling = true;
imageTemplate.strokeOpacity = 0;
imageTemplate.fillOpacity = 0.4;
imageTemplate.tooltipText = "{name}: [bold]{value}[/]";
// this is needed for the tooltip to point to the top of the circle instead of the middle
//imageTemplate.adapter.add("tooltipY", function(tooltipY, target) {
// return -target.children.getIndex(0).radius;
//})
imageTemplate.events.on("over", handleImageOver);
imageTemplate.events.on("out", handleImageOut);
imageTemplate.events.on("hit", handleImageHit);
// When hovered, circles become non-opaque
var imageHoverState = imageTemplate.states.create("hover");
imageHoverState.properties.fillOpacity = 1;
// add circle inside the image
var circle = imageTemplate.createChild(am4core.Circle);
// this makes the circle to pulsate a bit when showing it
circle.hiddenState.properties.scale = 0.0001;
circle.hiddenState.transitionDuration = 2000;
circle.defaultState.transitionDuration = 2000;
circle.defaultState.transitionEasing = am4core.ease.elasticOut;
// later we set fill color on template (when changing what type of data the map should show) and all the clones get the color because of this
circle.applyOnClones = true;
// heat rule makes the bubbles to be of a different width. Adjust min/max for smaller/bigger radius of a bubble
bubbleSeries.heatRules.push({
"target": circle,
"property": "radius",
"min": 3,
"max": 30,
"dataField": "value"
})
// when data items validated, hide 0 value bubbles (because min size is set)
bubbleSeries.events.on("dataitemsvalidated", function() {
bubbleSeries.dataItems.each((dataItem) => {
var mapImage = dataItem.mapImage;
var circle = mapImage.children.getIndex(0);
if (mapImage.dataItem.value == 0) {
circle.hide(0);
}
else if (circle.isHidden || circle.isHiding) {
circle.show();
}
})
})
// this places bubbles at the visual center of a country
imageTemplate.adapter.add("latitude", function(latitude, target) {
var polygon = polygonSeries.getPolygonById(target.dataItem.id);
if (polygon) {
target.disabled = false;
return polygon.visualLatitude;
}
else {
target.disabled = true;
}
return latitude;
})
imageTemplate.adapter.add("longitude", function(longitude, target) {
var polygon = polygonSeries.getPolygonById(target.dataItem.id);
if (polygon) {
target.disabled = false;
return polygon.visualLongitude;
}
else {
target.disabled = true;
}
return longitude;
})
// END OF MAP
// top title
var title = mapChart.titles.create();
title.fontSize = "1.5em";
title.text = "COVID-19 Spread Data";
title.align = "left";
title.horizontalCenter = "left";
title.marginLeft = 20;
title.paddingBottom = 10;
title.fill = am4core.color("#ffffff");
title.y = 20;
// switch between map and globe
var mapGlobeSwitch = mapChart.createChild(am4core.SwitchButton);
mapGlobeSwitch.align = "right"
mapGlobeSwitch.y = 15;
mapGlobeSwitch.leftLabel.text = "Map";
mapGlobeSwitch.leftLabel.fill = am4core.color("#ffffff");
mapGlobeSwitch.rightLabel.fill = am4core.color("#ffffff");
mapGlobeSwitch.rightLabel.text = "Globe";
mapGlobeSwitch.verticalCenter = "top";
mapGlobeSwitch.events.on("toggled", function() {
if (mapGlobeSwitch.isActive) {
mapChart.projection = new am4maps.projections.Orthographic;
mapChart.backgroundSeries.show();
mapChart.panBehavior = "rotateLongLat";
polygonSeries.exclude = [];
} else {
mapChart.projection = new am4maps.projections.Miller;
mapChart.backgroundSeries.hide();
mapChart.panBehavior = "move";
polygonSeries.data = [];
polygonSeries.exclude = ["AQ"];
}
})
// buttons & chart container
var buttonsAndChartContainer = container.createChild(am4core.Container);
buttonsAndChartContainer.layout = "vertical";
buttonsAndChartContainer.height = am4core.percent(40); // make this bigger if you want more space for the chart
buttonsAndChartContainer.width = am4core.percent(100);
buttonsAndChartContainer.valign = "bottom";
// country name and buttons container
var nameAndButtonsContainer = buttonsAndChartContainer.createChild(am4core.Container)
nameAndButtonsContainer.width = am4core.percent(100);
nameAndButtonsContainer.padding(0, 10, 5, 20);
nameAndButtonsContainer.layout = "horizontal";
// name of a country and date label
var countryName = nameAndButtonsContainer.createChild(am4core.Label);
countryName.fontSize = "1.1em";
countryName.fill = am4core.color("#ffffff");
countryName.valign = "middle";
// buttons container (active/confirmed/recovered/deaths)
var buttonsContainer = nameAndButtonsContainer.createChild(am4core.Container);
buttonsContainer.layout = "grid";
buttonsContainer.width = am4core.percent(100);
buttonsContainer.x = 10;
buttonsContainer.contentAlign = "right";
// Chart & slider container
var chartAndSliderContainer = buttonsAndChartContainer.createChild(am4core.Container);
chartAndSliderContainer.layout = "vertical";
chartAndSliderContainer.height = am4core.percent(100);
chartAndSliderContainer.width = am4core.percent(100);
chartAndSliderContainer.background = new am4core.RoundedRectangle();
chartAndSliderContainer.background.fill = am4core.color("#000000");
chartAndSliderContainer.background.cornerRadius(30, 30, 0, 0)
chartAndSliderContainer.background.fillOpacity = 0.25;
chartAndSliderContainer.paddingTop = 12;
chartAndSliderContainer.paddingBottom = 0;
// Slider container
var sliderContainer = chartAndSliderContainer.createChild(am4core.Container);
sliderContainer.width = am4core.percent(100);
sliderContainer.padding(0, 15, 15, 10);
sliderContainer.layout = "horizontal";
var slider = sliderContainer.createChild(am4core.Slider);
slider.width = am4core.percent(100);
slider.valign = "middle";
slider.background.opacity = 0.4;
slider.opacity = 0.7;
slider.background.fill = am4core.color("#ffffff");
slider.marginLeft = 20;
slider.marginRight = 35;
slider.height = 15;
slider.start = 1;
// what to do when slider is dragged
slider.events.on("rangechanged", function(event) {
var index = Math.round((covid_world_timeline.length - 1) * slider.start);
updateMapData(getSlideData(index).list);
updateTotals(index);
})
// stop animation if dragged
slider.startGrip.events.on("drag", () => {
stop();
if (sliderAnimation) {
sliderAnimation.setProgress(slider.start);
}
});
// play button
var playButton = sliderContainer.createChild(am4core.PlayButton);
playButton.valign = "middle";
// play button behavior
playButton.events.on("toggled", function(event) {
if (event.target.isActive) {
play();
} else {
stop();
}
})
// make slider grip look like play button
slider.startGrip.background.fill = playButton.background.fill;
slider.startGrip.background.strokeOpacity = 0;
slider.startGrip.icon.stroke = am4core.color("#ffffff");
slider.startGrip.background.states.copyFrom(playButton.background.states)
// bubble size slider
var sizeSlider = container.createChild(am4core.Slider);
sizeSlider.orientation = "vertical";
sizeSlider.height = am4core.percent(12);
sizeSlider.marginLeft = 25;
sizeSlider.align = "left";
sizeSlider.valign = "top";
sizeSlider.verticalCenter = "middle";
sizeSlider.opacity = 0.7;
sizeSlider.background.fill = am4core.color("#ffffff");
sizeSlider.adapter.add("y", function(y, target) {
return container.pixelHeight * (1 - buttonsAndChartContainer.percentHeight / 100) * 0.25;
})
sizeSlider.startGrip.background.fill = activeColor;
sizeSlider.startGrip.background.fillOpacity = 0.8;
sizeSlider.startGrip.background.strokeOpacity = 0;
sizeSlider.startGrip.icon.stroke = am4core.color("#ffffff");
sizeSlider.startGrip.background.states.getKey("hover").properties.fill = activeColor;
sizeSlider.startGrip.background.states.getKey("down").properties.fill = activeColor;
sizeSlider.horizontalCenter = "middle";
sizeSlider.events.on("rangechanged", function() {
sizeSlider.startGrip.scale = 0.75 + sizeSlider.start;
bubbleSeries.heatRules.getIndex(0).max = 30 + sizeSlider.start * 100;
circle.clones.each(function(clone) {
clone.radius = clone.radius;
})
})
var sizeLabel = container.createChild(am4core.Label);
sizeLabel.text = "max bubble size *";
sizeLabel.fill = am4core.color("#ffffff");
sizeLabel.rotation = 90;
sizeLabel.fontSize = "0.8em";
sizeLabel.fillOpacity = 0.5;
sizeLabel.horizontalCenter = "middle";
sizeLabel.align = "left"
sizeLabel.paddingBottom = 40;
sizeLabel.tooltip = new am4core.Tooltip();
sizeLabel.tooltip.setBounds({ x: 0, y: 0, width: 200000, height: 200000 })
sizeLabel.tooltip.label.wrap = true;
sizeLabel.tooltip.label.maxWidth = 300;
sizeLabel.tooltipText = "Some countries have so many cases that bubbles for countries with smaller values often look the same even if there is a significant difference between them. This slider can be used to increase maximum size of a bubble so that when you zoom in to a region with relatively small values you could compare them anyway."
sizeLabel.fill = am4core.color("#ffffff");
sizeLabel.adapter.add("y", function(y, target) {
return container.pixelHeight * (1 - buttonsAndChartContainer.percentHeight / 100) * 0.25;
})
// filter slider
// bubble size slider
var filterSlider = container.createChild(am4core.Slider);
filterSlider.orientation = "vertical";
filterSlider.height = am4core.percent(28);
filterSlider.marginLeft = 25;
filterSlider.align = "left";
filterSlider.valign = "top";
filterSlider.verticalCenter = "middle";
filterSlider.opacity = 0.7;
filterSlider.background.fill = am4core.color("#ffffff");
filterSlider.adapter.add("y", function(y, target) {
return container.pixelHeight * (1 - buttonsAndChartContainer.percentHeight / 100) * 0.7;
})
filterSlider.startGrip.background.fill = activeColor;
filterSlider.startGrip.background.fillOpacity = 0.8;
filterSlider.startGrip.background.strokeOpacity = 0;
filterSlider.startGrip.icon.stroke = am4core.color("#ffffff");
filterSlider.startGrip.background.states.getKey("hover").properties.fill = activeColor;
filterSlider.startGrip.background.states.getKey("down").properties.fill = activeColor;
filterSlider.horizontalCenter = "middle";
filterSlider.start = 1;
filterSlider.events.on("rangechanged", function() {
var maxValue = max[currentType] * filterSlider.start + 1;
if (!isNaN(maxValue) && bubbleSeries.inited) {
bubbleSeries.heatRules.getIndex(0).maxValue = maxValue;
circle.clones.each(function(clone) {
if (clone.dataItem.value > maxValue) {
clone.dataItem.hide();
}
else {
clone.dataItem.show();
}
clone.radius = clone.radius;
})
}
})
var filterLabel = container.createChild(am4core.Label);
filterLabel.text = "filter max values *";
filterLabel.rotation = 90;
filterLabel.fill = am4core.color("#ffffff");
filterLabel.fontSize = "0.8em";
filterLabel.fillOpacity = 0.5;
filterLabel.horizontalCenter = "middle";
filterLabel.align = "left"
filterLabel.paddingBottom = 40;
filterLabel.tooltip = new am4core.Tooltip();
filterLabel.tooltip.setBounds({ x: 0, y: 0, width: 200000, height: 200000 })
filterLabel.tooltip.label.wrap = true;
filterLabel.tooltip.label.maxWidth = 300;
filterLabel.tooltipText = "This filter allows to remove countries with many cases from the map so that it would be possible to compare countries with smaller number of cases."
filterLabel.fill = am4core.color("#ffffff");
filterLabel.adapter.add("y", function(y, target) {
return container.pixelHeight * (1 - buttonsAndChartContainer.percentHeight / 100) * 0.7;
})
// play behavior
function play() {
if (!sliderAnimation) {
sliderAnimation = slider.animate({ property: "start", to: 1, from: 0 }, 50000, am4core.ease.linear).pause();
sliderAnimation.events.on("animationended", () => {
playButton.isActive = false;
})
}
if (slider.start >= 1) {
slider.start = 0;
sliderAnimation.start();
}
sliderAnimation.resume();
playButton.isActive = true;
}
// stop behavior
function stop() {
if (sliderAnimation) {
sliderAnimation.pause();
}
playButton.isActive = false;
}
// BOTTOM CHART
// https://www.amcharts.com/docs/v4/chart-types/xy-chart/
var lineChart = chartAndSliderContainer.createChild(am4charts.XYChart);
lineChart.fontSize = "0.8em";
lineChart.paddingRight = 30;
lineChart.paddingLeft = 30;
lineChart.maskBullets = false;
lineChart.zoomOutButton.disabled = true;
lineChart.paddingBottom = 5;
lineChart.paddingTop = 3;
// make a copy of data as we will be modifying it
lineChart.data = JSON.parse(JSON.stringify(covid_total_timeline));
// date axis
// https://www.amcharts.com/docs/v4/concepts/axes/date-axis/
var dateAxis = lineChart.xAxes.push(new am4charts.DateAxis());
dateAxis.renderer.minGridDistance = 50;
dateAxis.renderer.grid.template.stroke = am4core.color("#000000");
dateAxis.renderer.grid.template.strokeOpacity = 0.25;
dateAxis.max = lastDate.getTime() + am4core.time.getDuration("day", 3);
dateAxis.tooltip.label.fontSize = "0.8em";
dateAxis.tooltip.background.fill = activeColor;
dateAxis.tooltip.background.stroke = activeColor;
dateAxis.renderer.labels.template.fill = am4core.color("#ffffff");
// value axis
// https://www.amcharts.com/docs/v4/concepts/axes/value-axis/
var valueAxis = lineChart.yAxes.push(new am4charts.ValueAxis());
valueAxis.interpolationDuration = 3000;
valueAxis.renderer.grid.template.stroke = am4core.color("#000000");
valueAxis.renderer.grid.template.strokeOpacity = 0.25;
valueAxis.renderer.baseGrid.disabled = true;
valueAxis.tooltip.disabled = true;
valueAxis.renderer.minGridDistance = 20;
valueAxis.extraMax = 0.05;
valueAxis.maxPrecision = 0;
valueAxis.renderer.inside = true;
valueAxis.renderer.labels.template.verticalCenter = "bottom";
valueAxis.renderer.labels.template.fill = am4core.color("#ffffff");
valueAxis.renderer.labels.template.padding(2, 2, 2, 2);
valueAxis.adapter.add("max", function(max, target) {
if (max < 5) {
max = 5
}
return max;
})
valueAxis.adapter.add("min", function(min, target) {
if (min < 0) {
min = 0;
}
return min;
})
// cursor
// https://www.amcharts.com/docs/v4/concepts/chart-cursor/
lineChart.cursor = new am4charts.XYCursor();
lineChart.cursor.behavior = "none"; // set zoomX for a zooming possibility
lineChart.cursor.lineY.disabled = true;
lineChart.cursor.lineX.stroke = activeColor;
lineChart.cursor.xAxis = dateAxis;
// this prevents cursor to move to the clicked location while map is dragged
am4core.getInteraction().body.events.off("down", lineChart.cursor.handleCursorDown, lineChart.cursor)
am4core.getInteraction().body.events.off("up", lineChart.cursor.handleCursorUp, lineChart.cursor)
// legend
// https://www.amcharts.com/docs/v4/concepts/legend/
lineChart.legend = new am4charts.Legend();
lineChart.legend.parent = lineChart.plotContainer;
lineChart.legend.labels.template.fill = am4core.color("#ffffff");
// create series
var activeSeries = addSeries("active", activeColor);
// active series is visible initially
activeSeries.tooltip.disabled = true;
activeSeries.hidden = false;
var confirmedSeries = addSeries("confirmed", confirmedColor);
var recoveredSeries = addSeries("recovered", recoveredColor);
var deathsSeries = addSeries("deaths", deathsColor);
var series = { active: activeSeries, confirmed: confirmedSeries, recovered: recoveredSeries, deaths: deathsSeries };
// add series
function addSeries(name, color) {
var series = lineChart.series.push(new am4charts.LineSeries())
series.dataFields.valueY = name;
series.dataFields.dateX = "date";
series.name = capitalizeFirstLetter(name);
series.strokeOpacity = 0.6;
series.stroke = color;
series.maskBullets = false;
series.minBulletDistance = 10;
series.hidden = true;
series.hideTooltipWhileZooming = true;
// series bullet
var bullet = series.bullets.push(new am4charts.CircleBullet());
// only needed to pass it to circle
var bulletHoverState = bullet.states.create("hover");
bullet.setStateOnChildren = true;
bullet.circle.fillOpacity = 1;
bullet.circle.fill = backgroundColor;
bullet.circle.radius = 2;
var circleHoverState = bullet.circle.states.create("hover");
circleHoverState.properties.fillOpacity = 1;
circleHoverState.properties.fill = color;
circleHoverState.properties.scale = 1.4;
// tooltip setup
series.tooltip.pointerOrientation = "down";
series.tooltip.getStrokeFromObject = true;
series.tooltip.getFillFromObject = false;
series.tooltip.background.fillOpacity = 0.2;
series.tooltip.background.fill = am4core.color("#000000");
series.tooltip.dy = -4;
series.tooltip.fontSize = "0.8em";
series.tooltipText = "{valueY}";
return series;
}
lineChart.plotContainer.events.on("up", function(){
slider.start = lineChart.cursor.xPosition * ((dateAxis.max - dateAxis.min) / (lastDate.getTime() - dateAxis.min));
})
// data warning label
var label = lineChart.plotContainer.createChild(am4core.Label);
label.text = "Current day stats may be incomplete until countries submit their data.";
label.fill = am4core.color("#ffffff");
label.fontSize = "0.7em";
label.opacity = 0.5;
label.align = "right";
label.horizontalCenter = "right";
label.verticalCenter = "bottom";
// BUTTONS
// create buttons
var activeButton = addButton("active", activeColor);
var confirmedButton = addButton("confirmed", confirmedColor);
var recoveredButton = addButton("recovered", recoveredColor);
var deathsButton = addButton("deaths", deathsColor);
var buttons = { active: activeButton, confirmed: confirmedButton, recovered: recoveredButton, deaths: deathsButton };
// add button
function addButton(name, color) {
var button = buttonsContainer.createChild(am4core.Button)
button.label.valign = "middle"
button.label.fill = am4core.color("#ffffff");
button.fontSize = 12;
button.background.cornerRadius(30, 30, 30, 30);
button.background.strokeOpacity = 0.3
button.background.fillOpacity = 0;
button.background.stroke = buttonStrokeColor;
button.background.padding(2, 3, 2, 3);
button.states.create("active");
button.setStateOnChildren = true;
var activeHoverState = button.background.states.create("hoverActive");
activeHoverState.properties.fillOpacity = 0;
var circle = new am4core.Circle();
circle.radius = 8;
circle.fillOpacity = 0.3;
circle.fill = buttonStrokeColor;
circle.strokeOpacity = 0;
circle.valign = "middle";
circle.marginRight = 5;
button.icon = circle;
// save name to dummy data for later use
button.dummyData = name;
var circleActiveState = circle.states.create("active");
circleActiveState.properties.fill = color;
circleActiveState.properties.fillOpacity = 0.5;
button.events.on("hit", handleButtonClick);
return button;
}
// handle button clikc
function handleButtonClick(event) {
// we saved name to dummy data
changeDataType(event.target.dummyData);
}
// change data type (active/confirmed/recovered/deaths)
function changeDataType(name) {
currentType = name;
// make button active
var activeButton = buttons[name];
activeButton.isActive = true;
// make other buttons inactive
for (var key in buttons) {
if (buttons[key] != activeButton) {
buttons[key].isActive = false;
}
}
// tell series new field name
bubbleSeries.dataFields.value = name;
bubbleSeries.invalidateData();
// change color of bubbles
// setting colors on mapImage for tooltip colors
bubbleSeries.mapImages.template.fill = colors[name];
bubbleSeries.mapImages.template.stroke = colors[name];
// first child is circle
bubbleSeries.mapImages.template.children.getIndex(0).fill = colors[name];
dateAxis.tooltip.background.fill = colors[name];
dateAxis.tooltip.background.stroke = colors[name];
lineChart.cursor.lineX.stroke = colors[name];
// show series
var activeSeries = series[name];
activeSeries.show();
// hide other series
for (var key in series) {
if (series[key] != activeSeries) {
series[key].hide();
}
}
// update heat rule's maxValue
bubbleSeries.heatRules.getIndex(0).maxValue = max[name];
}
// select a country
function selectCountry(mapPolygon) {
resetHover();
polygonSeries.hideTooltip();
// if the same country is clicked show world
if (currentPolygon == mapPolygon) {
currentPolygon.isActive = false;
currentPolygon = undefined;
showWorld();
return;
}
// save current polygon
currentPolygon = mapPolygon;
var countryIndex = countryIndexMap[mapPolygon.dataItem.id];
currentCountry = mapPolygon.dataItem.dataContext.name;
// make others inactive
polygonSeries.mapPolygons.each(function(polygon) {
polygon.isActive = false;
})
// clear timeout if there is one
if (countryDataTimeout) {
clearTimeout(countryDataTimeout);
}
// we delay change of data for better performance (so that data is not changed whil zooming)
countryDataTimeout = setTimeout(function() {
setCountryData(countryIndex);
}, 1000); // you can adjust number, 1000 is one second
updateTotals(currentIndex);
updateCountryName();
mapPolygon.isActive = true;
// meaning it's globe
if (mapGlobeSwitch.isActive) {
// animate deltas (results the map to be rotated to the selected country)
if (mapChart.zoomLevel != 1) {
mapChart.goHome();
rotateAndZoom(mapPolygon);
}
else {
rotateAndZoom(mapPolygon);
}
}
// if it's not a globe, simply zoom to the country
else {
mapChart.zoomToMapObject(mapPolygon, getZoomLevel(mapPolygon));
}
}
// change line chart data to the selected countries
function setCountryData(countryIndex) {
// instead of setting whole data array, we modify current raw data so that a nice animation would happen
for (var i = 0; i < lineChart.data.length; i++) {
var di = covid_world_timeline[i].list;
var countryData = di[countryIndex];
var dataContext = lineChart.data[i];
if (countryData) {
dataContext.recovered = countryData.recovered;
dataContext.confirmed = countryData.confirmed;
dataContext.deaths = countryData.deaths;
dataContext.active = countryData.confirmed - countryData.recovered - countryData.deaths;
valueAxis.min = undefined;
valueAxis.max = undefined;
}
else {
dataContext.recovered = 0;
dataContext.confirmed = 0;
dataContext.deaths = 0;
dataContext.active = 0;
valueAxis.min = 0;
valueAxis.max = 10;
}
}
lineChart.invalidateRawData();
updateTotals(currentIndex);
setTimeout(updateSeriesTooltip, 1000);
}
function updateSeriesTooltip() {
var position = dateAxis.dateToPosition(currentDate);
position = dateAxis.toGlobalPosition(position);
var x = dateAxis.positionToCoordinate(position);
lineChart.cursor.triggerMove({ x: x, y: 0 }, "soft", true);
lineChart.series.each(function(series) {
if (!series.isHidden) {
series.tooltip.disabled = false;
series.showTooltipAtDataItem(series.tooltipDataItem);
}
})
}
// what happens when a country is rolled-over
function rollOverCountry(mapPolygon) {
resetHover();
if (mapPolygon) {
mapPolygon.isHover = true;
// make bubble hovered too
var image = bubbleSeries.getImageById(mapPolygon.dataItem.id);
if (image) {
image.dataItem.dataContext.name = mapPolygon.dataItem.dataContext.name;
image.isHover = true;
}
}
}
// what happens when a country is rolled-out
function rollOutCountry(mapPolygon) {
var image = bubbleSeries.getImageById(mapPolygon.dataItem.id)
resetHover();
if (image) {
image.isHover = false;
}
}
// rotate and zoom
function rotateAndZoom(mapPolygon) {
polygonSeries.hideTooltip();
var animation = mapChart.animate([{ property: "deltaLongitude", to: -mapPolygon.visualLongitude }, { property: "deltaLatitude", to: -mapPolygon.visualLatitude }], 1000)
animation.events.on("animationended", function() {
mapChart.zoomToMapObject(mapPolygon, getZoomLevel(mapPolygon));
})
}
// calculate zoom level (default is too close)
function getZoomLevel(mapPolygon) {
var w = mapPolygon.polygon.bbox.width;
var h = mapPolygon.polygon.bbox.width;
// change 2 to smaller walue for a more close zoom
return Math.min(mapChart.seriesWidth / (w * 2), mapChart.seriesHeight / (h * 2))
}
// show world data
function showWorld() {
currentCountry = "World";
currentPolygon = undefined;
resetHover();
if (countryDataTimeout) {
clearTimeout(countryDataTimeout);
}
// make all inactive
polygonSeries.mapPolygons.each(function(polygon) {
polygon.isActive = false;
})
updateCountryName();
// update line chart data (again, modifying instead of setting new data for a nice animation)
for (var i = 0; i < lineChart.data.length; i++) {
var di = covid_total_timeline[i];
var dataContext = lineChart.data[i];
dataContext.recovered = di.recovered;
dataContext.confirmed = di.confirmed;
dataContext.deaths = di.deaths;
dataContext.active = di.confirmed - di.recovered;
valueAxis.min = undefined;
valueAxis.max = undefined;
}
lineChart.invalidateRawData();
updateTotals(currentIndex);
mapChart.goHome();
}
// updates country name and date
function updateCountryName() {
countryName.text = currentCountry + ", " + mapChart.dateFormatter.format(currentDate, "MMM dd, yyyy");
}
// update total values in buttons
function updateTotals(index) {
if (!isNaN(index)) {
var di = covid_total_timeline[index];
var date = new Date(di.date);
currentDate = date;
updateCountryName();
var position = dateAxis.dateToPosition(date);
position = dateAxis.toGlobalPosition(position);
var x = dateAxis.positionToCoordinate(position);
if (lineChart.cursor) {
lineChart.cursor.triggerMove({ x: x, y: 0 }, "soft", true);
}
for (var key in buttons) {
buttons[key].label.text = capitalizeFirstLetter(key) + ": " + lineChart.data[index][key];
}
currentIndex = index;
}
}
// update map data
function updateMapData(data) {
//modifying instead of setting new data for a nice animation
bubbleSeries.dataItems.each(function(dataItem) {
dataItem.dataContext.confirmed = 0;
dataItem.dataContext.deaths = 0;
dataItem.dataContext.recovered = 0;
dataItem.dataContext.active = 0;
})
for (var i = 0; i < data.length; i++) {
var di = data[i];
var image = bubbleSeries.getImageById(di.id);
if (image) {
image.dataItem.dataContext.confirmed = di.confirmed;
image.dataItem.dataContext.deaths = di.deaths;
image.dataItem.dataContext.recovered = di.recovered;
image.dataItem.dataContext.active = di.confirmed - di.recovered - di.deaths;
}
}
bubbleSeries.invalidateRawData();
}
// capitalize first letter
function capitalizeFirstLetter(string) {
return string.charAt(0).toUpperCase() + string.slice(1);
}
function handleImageOver(event) {
rollOverCountry(polygonSeries.getPolygonById(event.target.dataItem.id));
}
function handleImageOut(event) {
rollOutCountry(polygonSeries.getPolygonById(event.target.dataItem.id));
}
function handleImageHit(event) {
selectCountry(polygonSeries.getPolygonById(event.target.dataItem.id));
}
function handleCountryHit(event) {
selectCountry(event.target);
}
function handleCountryOver(event) {
rollOverCountry(event.target);
}
function handleCountryOut(event) {
rollOutCountry(event.target);
}
function resetHover() {
polygonSeries.mapPolygons.each(function(polygon) {
polygon.isHover = false;
})
bubbleSeries.mapImages.each(function(image) {
image.isHover = false;
})
}
container.events.on("layoutvalidated", function() {
dateAxis.tooltip.hide();
lineChart.cursor.hide();
updateTotals(currentIndex);
});
// set initial data and names
updateCountryName();
changeDataType("active");
setTimeout(updateSeriesTooltip, 3000);
}); // end am4core.ready()
</script>
<!-- HTML -->
<br />
<div dir="ltr" id="chartdiv">
</div>
</div>