svgmap
Version:
svgMap is a JavaScript library that lets you easily create an interactable world map comparing customizable data for each country.
1,579 lines (1,456 loc) • 157 kB
JavaScript
class svgMap {
constructor(options = {}) {
const defaultOptions = {
// The element to render the map in
targetElementID: '',
// Allow user to zoom and pan
allowInteraction: true,
// Minimum and maximum zoom
minZoom: 1,
maxZoom: 25,
// Initial zoom
initialZoom: 1.06,
// Initial pan
initialPan: {
x: 0,
y: 0
},
// Zoom sensitivity
zoomScaleSensitivity: 0.2,
// Zoom with double-click
dblClickZoomEnabled: true,
// Zoom with mousewheel
mouseWheelZoomEnabled: true,
// Allow zooming only when one of the following keys is pressed: 'shift', 'control', 'alt' (Windows), 'command' (MacOS), 'option' (MacOS)
mouseWheelZoomWithKey: false,
// The message to show for non MacOS systems
mouseWheelKeyMessage: 'Press the [ALT] key to zoom',
// The message to show for MacOS
mouseWheelKeyMessageMac: 'Press the [COMMAND] key to zoom',
// Position of the zoom buttons
zoomButtonsPosition: 'bottomLeft',
// Data colors
colorMax: '#CC0033',
colorMin: '#FFE5D9',
colorNoData: '#E2E2E2',
// Ratio type for the color scale, can be 'linear' or 'log' for logarithmic or a custom function (value, min, max) => ratio
ratioType: 'linear',
// The flag type can be 'image' or 'emoji'
flagType: 'image',
// The URL to the flags when using flag type 'image', {0} will get replaced with the lowercase country id
flagURL:
'https://cdn.jsdelivr.net/gh/hjnilsson/country-flags@latest/svg/{0}.svg',
// Decide whether to show the flag option or not
hideFlag: false,
// Whether attributes with no data should be displayed
hideMissingData: false,
// The default text to be shown when no data is present
noDataText: 'No data available',
// Set to true to open the link on mobile devices, set to false (default) to show the tooltip
touchLink: false,
// Set to true to show the to show a zoom reset button
showZoomReset: false,
// Called when a tooltip is created to custimize the tooltip content
onGetTooltip: function (tooltipDiv, countryID, countryValues) {
return null;
},
// Country specific options
countries: {
// Western Sahara: Set to false to combine Morocco (MA) and Western Sahara (EH)
EH: true
},
// Set to true to show a drop down menu with the continents
showContinentSelector: false,
// Reset zoom on resize
resetZoomOnResize: false
};
this.options = Object.assign({}, defaultOptions, options);
this.init();
}
// Initialize
init() {
// Abort if target element not found
if (
!this.options.targetElementID ||
!document.getElementById(this.options.targetElementID)
) {
this.error('Target element not found');
}
// Abort if no data
if (!this.options.data) {
this.error('No data');
}
// Global id
this.id = this.options.targetElementID;
// Wrapper element
this.wrapper = document.getElementById(this.options.targetElementID);
this.wrapper.classList.add('svgMap-wrapper');
// Container element
this.container = document.createElement('div');
this.container.classList.add('svgMap-container');
this.wrapper.appendChild(this.container);
// Block scrolling when option is enabled
if (
this.options.allowInteraction &&
this.options.mouseWheelZoomEnabled &&
this.options.mouseWheelZoomWithKey
) {
this.addMouseWheelZoomNotice();
this.addMouseWheelZoomWithKeyEvents();
}
// Map container element
this.mapContainer = document.createElement('div');
this.mapContainer.classList.add('svgMap-map-container');
this.container.appendChild(this.mapContainer);
// Create the map
this.createMap();
// Apply map data
this.applyData(this.options.data);
}
// Countries
countries = {
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',
BR: 'Brazil',
IO: 'British Indian Ocean Territory',
VG: 'British Virgin Islands',
BN: 'Brunei Darussalam',
BG: 'Bulgaria',
BF: 'Burkina Faso',
BI: 'Burundi',
KH: 'Cambodia',
CM: 'Cameroon',
CA: 'Canada',
CV: 'Cape Verde',
BQ: 'Caribbean Netherlands',
KY: 'Cayman Islands',
CF: 'Central African Republic',
TD: 'Chad',
CL: 'Chile',
CN: 'China',
CX: 'Christmas Island',
CC: 'Cocos Islands',
CO: 'Colombia',
KM: 'Comoros',
CG: 'Congo',
CK: 'Cook Islands',
CR: 'Costa Rica',
HR: 'Croatia',
CU: 'Cuba',
CW: 'Curaçao',
CY: 'Cyprus',
CZ: 'Czech Republic',
CD: 'Democratic Republic of the Congo',
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',
FO: 'Faroe Islands',
FM: 'Federated States of Micronesia',
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',
GN: 'Guinea',
GW: 'Guinea-Bissau',
GY: 'Guyana',
HT: 'Haiti',
HN: 'Honduras',
HK: 'Hong Kong',
HU: 'Hungary',
IS: 'Iceland',
IN: 'India',
ID: 'Indonesia',
IR: 'Iran',
IQ: 'Iraq',
IE: 'Ireland',
IM: 'Isle of Man',
IL: 'Israel',
IT: 'Italy',
CI: 'Ivory Coast',
JM: 'Jamaica',
JP: 'Japan',
JE: 'Jersey',
JO: 'Jordan',
KZ: 'Kazakhstan',
KE: 'Kenya',
KI: 'Kiribati',
XK: 'Kosovo',
KW: 'Kuwait',
KG: 'Kyrgyzstan',
LA: 'Laos',
LV: 'Latvia',
LB: 'Lebanon',
LS: 'Lesotho',
LR: 'Liberia',
LY: 'Libya',
LI: 'Liechtenstein',
LT: 'Lithuania',
LU: 'Luxembourg',
MO: 'Macau',
MK: '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',
MD: 'Moldova',
MC: 'Monaco',
MN: 'Mongolia',
ME: 'Montenegro',
MS: 'Montserrat',
MA: 'Morocco',
MZ: 'Mozambique',
MM: 'Myanmar',
NA: 'Namibia',
NR: 'Nauru',
NP: 'Nepal',
NL: 'Netherlands',
NC: 'New Caledonia',
NZ: 'New Zealand',
NI: 'Nicaragua',
NE: 'Niger',
NG: 'Nigeria',
NU: 'Niue',
NF: 'Norfolk Island',
KP: 'North Korea',
MP: 'Northern Mariana Islands',
NO: 'Norway',
OM: 'Oman',
PK: 'Pakistan',
PW: 'Palau',
PS: 'Palestine',
PA: 'Panama',
PG: 'Papua New Guinea',
PY: 'Paraguay',
PE: 'Peru',
PH: 'Philippines',
PN: 'Pitcairn Islands',
PL: 'Poland',
PT: 'Portugal',
PR: 'Puerto Rico',
QA: 'Qatar',
RE: 'Reunion',
RO: 'Romania',
RU: 'Russia',
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: 'São Tomé and Príncipe',
SA: 'Saudi Arabia',
SN: 'Senegal',
RS: 'Serbia',
SC: 'Seychelles',
SL: 'Sierra Leone',
SG: 'Singapore',
SX: 'Sint Maarten',
SK: 'Slovakia',
SI: 'Slovenia',
SB: 'Solomon Islands',
SO: 'Somalia',
ZA: 'South Africa',
GS: 'South Georgia and the South Sandwich Islands',
KR: 'South Korea',
SS: 'South Sudan',
ES: 'Spain',
LK: 'Sri Lanka',
SD: 'Sudan',
SR: 'Suriname',
SJ: 'Svalbard and Jan Mayen',
SZ: 'Eswatini',
SE: 'Sweden',
CH: 'Switzerland',
SY: 'Syria',
TW: 'Taiwan',
TJ: 'Tajikistan',
TZ: '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',
AE: 'United Arab Emirates',
GB: 'United Kingdom',
US: 'United States',
UM: 'United States Minor Outlying Islands',
VI: 'United States Virgin Islands',
UY: 'Uruguay',
UZ: 'Uzbekistan',
VU: 'Vanuatu',
VA: 'Vatican City',
VE: 'Venezuela',
VN: 'Vietnam',
WF: 'Wallis and Futuna',
EH: 'Western Sahara',
YE: 'Yemen',
ZM: 'Zambia',
ZW: 'Zimbabwe'
};
// Apply the data to the map
applyData(data) {
var max = null;
var min = null;
// Get highest and lowest value
Object.keys(data.values).forEach(function (countryID) {
var value = parseInt(data.values[countryID][data.applyData], 10);
max === null && (max = value);
min === null && (min = value);
value > max && (max = value);
value < min && (min = value);
});
data.data[data.applyData].thresholdMax &&
(max = Math.min(max, data.data[data.applyData].thresholdMax));
data.data[data.applyData].thresholdMin &&
(min = Math.max(min, data.data[data.applyData].thresholdMin));
// Loop through countries and set colors
Object.keys(this.countries).forEach(
function (countryID) {
var element = document.getElementById(
this.id + '-map-country-' + countryID
);
if (!element) {
return;
}
if (!data.values[countryID]) {
element.style.setProperty(
'--svg-map-country-fill',
this.toHex(this.options.colorNoData)
);
return;
}
if (typeof data.values[countryID].color != 'undefined') {
element.style.setProperty(
'--svg-map-country-fill',
data.values[countryID].color
);
return;
}
var value = Math.max(
min,
parseInt(data.values[countryID][data.applyData], 10)
);
var color = this.getColor(
this.toHex(this.options.colorMax),
this.toHex(this.options.colorMin),
this.calculateColorRatio(value, min, max, this.options.ratioType)
);
element.style.setProperty('--svg-map-country-fill', color);
}.bind(this)
);
}
calculateColorRatio(value, min, max, ratioType) {
var range = max - min;
var positionInRange = value - min;
if (range === 0 || positionInRange === 0) {
return 0;
}
if (ratioType === 'log') {
var logValue = Math.log(positionInRange + 1);
var logMin = Math.log(1);
var logMax = Math.log(range + 1);
var ratio = Math.max(
0,
Math.min(1, (logValue - logMin) / (logMax - logMin))
);
return ratio || ratio === 0 ? ratio : 1;
}
if (ratioType === 'linear') {
var ratio = Math.max(0, Math.min(1, positionInRange / range));
return ratio || ratio === 0 ? ratio : 1;
}
if (typeof ratioType === 'function') {
return ratioType(value, min, max);
}
return 1;
}
// Emoji flags
emojiFlags = {
AF: '🇦🇫',
AX: '🇦🇽',
AL: '🇦🇱',
DZ: '🇩🇿',
AS: '🇦🇸',
AD: '🇦🇩',
AO: '🇦🇴',
AI: '🇦🇮',
AQ: '🇦🇶',
AG: '🇦🇬',
AR: '🇦🇷',
AM: '🇦🇲',
AW: '🇦🇼',
AU: '🇦🇺',
AT: '🇦🇹',
AZ: '🇦🇿',
BS: '🇧🇸',
BH: '🇧🇭',
BD: '🇧🇩',
BB: '🇧🇧',
BY: '🇧🇾',
BE: '🇧🇪',
BZ: '🇧🇿',
BJ: '🇧🇯',
BM: '🇧🇲',
BT: '🇧🇹',
BO: '🇧🇴',
BA: '🇧🇦',
BW: '🇧🇼',
BR: '🇧🇷',
IO: '🇮🇴',
VG: '🇻🇬',
BN: '🇧🇳',
BG: '🇧🇬',
BF: '🇧🇫',
BI: '🇧🇮',
KH: '🇰🇭',
CM: '🇨🇲',
CA: '🇨🇦',
CV: '🇨🇻',
BQ: '🇧🇶',
KY: '🇰🇾',
CF: '🇨🇫',
TD: '🇹🇩',
CL: '🇨🇱',
CN: '🇨🇳',
CX: '🇨🇽',
CC: '🇨🇨',
CO: '🇨🇴',
KM: '🇰🇲',
CG: '🇨🇬',
CK: '🇨🇰',
CR: '🇨🇷',
HR: '🇭🇷',
CU: '🇨🇺',
CW: '🇨🇼',
CY: '🇨🇾',
CZ: '🇨🇿',
CD: '🇨🇩',
DK: '🇩🇰',
DJ: '🇩🇯',
DM: '🇩🇲',
DO: '🇩🇴',
EC: '🇪🇨',
EG: '🇪🇬',
SV: '🇸🇻',
GQ: '🇬🇶',
ER: '🇪🇷',
EE: '🇪🇪',
ET: '🇪🇹',
FK: '🇫🇰',
FO: '🇫🇴',
FM: '🇫🇲',
FJ: '🇫🇯',
FI: '🇫🇮',
FR: '🇫🇷',
GF: '🇬🇫',
PF: '🇵🇫',
TF: '🇹🇫',
GA: '🇬🇦',
GM: '🇬🇲',
GE: '🇬🇪',
DE: '🇩🇪',
GH: '🇬🇭',
GI: '🇬🇮',
GR: '🇬🇷',
GL: '🇬🇱',
GD: '🇬🇩',
GP: '🇬🇵',
GU: '🇬🇺',
GT: '🇬🇹',
GN: '🇬🇳',
GW: '🇬🇼',
GY: '🇬🇾',
HT: '🇭🇹',
HN: '🇭🇳',
HK: '🇭🇰',
HU: '🇭🇺',
IS: '🇮🇸',
IN: '🇮🇳',
ID: '🇮🇩',
IR: '🇮🇷',
IQ: '🇮🇶',
IE: '🇮🇪',
IM: '🇮🇲',
IL: '🇮🇱',
IT: '🇮🇹',
CI: '🇨🇮',
JM: '🇯🇲',
JP: '🇯🇵',
JE: '🇯🇪',
JO: '🇯🇴',
KZ: '🇰🇿',
KE: '🇰🇪',
KI: '🇰🇮',
XK: '🇽🇰',
KW: '🇰🇼',
KG: '🇰🇬',
LA: '🇱🇦',
LV: '🇱🇻',
LB: '🇱🇧',
LS: '🇱🇸',
LR: '🇱🇷',
LY: '🇱🇾',
LI: '🇱🇮',
LT: '🇱🇹',
LU: '🇱🇺',
MO: '🇲🇴',
MK: '🇲🇰',
MG: '🇲🇬',
MW: '🇲🇼',
MY: '🇲🇾',
MV: '🇲🇻',
ML: '🇲🇱',
MT: '🇲🇹',
MH: '🇲🇭',
MQ: '🇲🇶',
MR: '🇲🇷',
MU: '🇲🇺',
YT: '🇾🇹',
MX: '🇲🇽',
MD: '🇲🇩',
MC: '🇲🇨',
MN: '🇲🇳',
ME: '🇲🇪',
MS: '🇲🇸',
MA: '🇲🇦',
MZ: '🇲🇿',
MM: '🇲🇲',
NA: '🇳🇦',
NR: '🇳🇷',
NP: '🇳🇵',
NL: '🇳🇱',
NC: '🇳🇨',
NZ: '🇳🇿',
NI: '🇳🇮',
NE: '🇳🇪',
NG: '🇳🇬',
NU: '🇳🇺',
NF: '🇳🇫',
KP: '🇰🇵',
MP: '🇲🇵',
NO: '🇳🇴',
OM: '🇴🇲',
PK: '🇵🇰',
PW: '🇵🇼',
PS: '🇵🇸',
PA: '🇵🇦',
PG: '🇵🇬',
PY: '🇵🇾',
PE: '🇵🇪',
PH: '🇵🇭',
PN: '🇵🇳',
PL: '🇵🇱',
PT: '🇵🇹',
PR: '🇵🇷',
QA: '🇶🇦',
RE: '🇷🇪',
RO: '🇷🇴',
RU: '🇷🇺',
RW: '🇷🇼',
SH: '🇸🇭',
KN: '🇰🇳',
LC: '🇱🇨',
PM: '🇵🇲',
VC: '🇻🇨',
WS: '🇼🇸',
SM: '🇸🇲',
ST: '🇸🇹',
SA: '🇸🇦',
SN: '🇸🇳',
RS: '🇷🇸',
SC: '🇸🇨',
SL: '🇸🇱',
SG: '🇸🇬',
SX: '🇸🇽',
SK: '🇸🇰',
SI: '🇸🇮',
SB: '🇸🇧',
SO: '🇸🇴',
ZA: '🇿🇦',
GS: '🇬🇸',
KR: '🇰🇷',
SS: '🇸🇸',
ES: '🇪🇸',
LK: '🇱🇰',
SD: '🇸🇩',
SR: '🇸🇷',
SJ: '🇸🇯',
SZ: '🇸🇿',
SE: '🇸🇪',
CH: '🇨🇭',
SY: '🇸🇾',
TW: '🇹🇼',
TJ: '🇹🇯',
TZ: '🇹🇿',
TH: '🇹🇭',
TL: '🇹🇱',
TG: '🇹🇬',
TK: '🇹🇰',
TO: '🇹🇴',
TT: '🇹🇹',
TN: '🇹🇳',
TR: '🇹🇷',
TM: '🇹🇲',
TC: '🇹🇨',
TV: '🇹🇻',
UG: '🇺🇬',
UA: '🇺🇦',
AE: '🇦🇪',
GB: '🇬🇧',
US: '🇺🇸',
UM: '🇺🇲',
VI: '🇻🇮',
UY: '🇺🇾',
UZ: '🇺🇿',
VU: '🇻🇺',
VA: '🇻🇦',
VE: '🇻🇪',
VN: '🇻🇳',
WF: '🇼🇫',
EH: '🇪🇭',
YE: '🇾🇪',
ZM: '🇿🇲',
ZW: '🇿🇼'
};
continents = {
EA: {
iso: 'EA',
name: 'World'
},
AF: {
iso: 'AF',
name: 'Africa',
pan: {
x: 454,
y: 250
},
zoom: 1.9
},
AS: {
iso: 'AS',
name: 'Asia',
pan: {
x: 904,
y: 80
},
zoom: 1.8
},
EU: {
iso: 'EU',
name: 'Europe',
pan: {
x: 404,
y: 80
},
zoom: 5
},
NA: {
iso: 'NA',
name: 'North America',
pan: {
x: 104,
y: 55
},
zoom: 2.6
},
MA: {
iso: 'MA',
name: 'Middle America',
pan: {
x: 104,
y: 200
},
zoom: 2.6
},
SA: {
iso: 'SA',
name: 'South America',
pan: {
x: 104,
y: 340
},
zoom: 2.2
},
OC: {
iso: 'OC',
name: 'Oceania',
pan: {
x: 954,
y: 350
},
zoom: 1.9
}
};
// Create the SVG map
createMap() {
// Create the tooltip
this.createTooltip();
// Create map wrappers
this.mapWrapper = this.createElement(
'div',
'svgMap-map-wrapper',
this.mapContainer
);
this.mapImage = document.createElementNS(
'http://www.w3.org/2000/svg',
'svg'
);
this.mapImage.setAttribute('viewBox', '0 0 2000 1001');
this.mapImage.classList.add('svgMap-map-image');
this.mapWrapper.appendChild(this.mapImage);
// Add controls
var mapControlsWrapper = this.createElement(
'div',
'svgMap-map-controls-wrapper',
this.mapWrapper
);
mapControlsWrapper.classList.add(
'svgMap-controls-position-' + this.options.zoomButtonsPosition
);
var zoomContainer = this.createElement(
'div',
'svgMap-map-controls-zoom',
mapControlsWrapper
);
['in', 'out', 'reset'].forEach(
function (item) {
if (
(item === 'reset' && this.options.showZoomReset) ||
item !== 'reset'
) {
var zoomControlName =
'zoomControl' + item.charAt(0).toUpperCase() + item.slice(1);
this[zoomControlName] = this.createElement(
'button',
'svgMap-control-button svgMap-zoom-button svgMap-zoom-' +
item +
'-button',
zoomContainer
);
this[zoomControlName].type = 'button';
this[zoomControlName].addEventListener(
'click',
function () {
if (this.options.allowInteraction) {
this.zoomMap(item);
}
}.bind(this),
{ passive: true }
);
}
}.bind(this)
);
if (!this.options.allowInteraction) {
mapControlsWrapper.classList.add('svgMap-disabled');
mapControlsWrapper.setAttribute('aria-disabled', 'true');
}
// Add accessible names to zoom controls
this.zoomControlIn.setAttribute('aria-label', 'Zoom in');
this.zoomControlOut.setAttribute('aria-label', 'Zoom out');
if (this.options.allowInteraction && this.options.showContinentSelector) {
// Add continent controls
var mapContinentControlsWrapper = this.createElement(
'div',
'svgMap-map-continent-controls-wrapper',
this.mapWrapper
);
this['continentSelect'] = this.createElement(
'select',
'svgMap-continent-select',
mapContinentControlsWrapper
);
var that = this;
Object.keys(this.continents).forEach(function (item) {
let element = that.createElement(
'option',
'svgMap-continent-option svgMap-continent-iso-' +
that.continents[item].iso,
that['continentSelect'],
that.continents[item].name
);
element.value = item;
});
this.continentSelect.addEventListener(
'change',
function (e) {
const continent = e.target.value;
if (continent) this.zoomContinent(e.target.value);
}.bind(that),
{ passive: true }
);
mapContinentControlsWrapper.setAttribute(
'aria-label',
'Select continent'
);
}
// Fix countries
var mapPaths = Object.assign({}, this.mapPaths);
if (!this.options.countries.EH) {
mapPaths.MA.d = mapPaths['MA-EH'].d;
delete mapPaths.EH;
}
delete mapPaths['MA-EH'];
// Expose tooltipMove function
this.tooltipMoveEvent = function (e) {
this.moveTooltip(e);
}.bind(this);
// Add map elements
Object.keys(mapPaths).forEach(
function (countryID) {
var countryData = this.mapPaths[countryID];
if (!countryData.d) {
return;
}
var countryElement = document.createElementNS(
'http://www.w3.org/2000/svg',
'path'
);
countryElement.setAttribute('d', countryData.d);
countryElement.setAttribute(
'id',
this.id + '-map-country-' + countryID
);
countryElement.setAttribute('data-id', countryID);
countryElement.classList.add('svgMap-country');
this.mapImage.appendChild(countryElement);
// Tooltip events
// Add tooltip when touch is used
countryElement.addEventListener(
'touchstart',
function (e) {
var activeCountries = document.querySelectorAll('.svgMap-active');
activeCountries.forEach(function (element) {
element.classList.remove('svgMap-active');
});
countryElement.parentNode.appendChild(countryElement);
countryElement.classList.add('svgMap-active');
var countryID = countryElement.getAttribute('data-id');
var countryLink = countryElement.getAttribute('data-link');
if (this.options.touchLink) {
if (countryLink) {
window.location.href = countryLink;
return;
}
}
this.setTooltipContent(this.getTooltipContent(countryID));
this.showTooltip(e);
this.moveTooltip(e);
countryElement.addEventListener(
'touchmove',
this.tooltipMoveEvent,
{
passive: true
}
);
}.bind(this),
{ passive: true }
);
countryElement.addEventListener(
'mouseenter',
function (e) {
countryElement.parentNode.appendChild(countryElement);
countryElement.classList.add('svgMap-active');
var countryID = countryElement.getAttribute('data-id');
this.setTooltipContent(this.getTooltipContent(countryID));
this.showTooltip(e);
countryElement.addEventListener(
'mousemove',
this.tooltipMoveEvent,
{
passive: true
}
);
}.bind(this),
{ passive: true }
);
if (
this.options.data.values &&
this.options.data.values[countryID] &&
this.options.data.values[countryID]['link']
) {
countryElement.setAttribute(
'data-link',
this.options.data.values[countryID]['link']
);
if (this.options.data.values[countryID]['linkTarget']) {
countryElement.setAttribute(
'data-link-target',
this.options.data.values[countryID]['linkTarget']
);
}
let dragged = false;
countryElement.addEventListener('mousedown', function () {
dragged = false;
});
countryElement.addEventListener('touchstart', function () {
dragged = false;
});
countryElement.addEventListener('mousemove', function () {
dragged = true;
});
countryElement.addEventListener('touchmove', function () {
dragged = true;
});
const clickHandler = function (e) {
if (dragged) {
return;
}
const link = countryElement.getAttribute('data-link');
const target = countryElement.getAttribute('data-link-target');
if (target) {
window.open(link, target);
} else {
window.location.href = link;
}
};
countryElement.addEventListener('click', clickHandler);
countryElement.addEventListener('touchend', clickHandler);
}
// Hide tooltip when mouse leaves the country area
countryElement.addEventListener(
'mouseleave',
function () {
this.hideTooltip();
countryElement.classList.remove('svgMap-active');
countryElement.removeEventListener(
'mousemove',
this.tooltipMoveEvent,
{
passive: true
}
);
}.bind(this),
{ passive: true }
);
// Hide tooltip when touch ends
countryElement.addEventListener(
'touchend',
function () {
// Do not hide the tooltip, so it stays open on mobile
}.bind(this),
{ passive: true }
);
// Show/hide tooltip on click
countryElement.addEventListener(
'click',
function (e) {
e.stopPropagation();
var countryID = countryElement.getAttribute('data-id');
if (countryElement.getAttribute('data-tooltip-open') === 'true') {
this.hideTooltip();
countryElement.setAttribute('data-tooltip-open', 'false');
} else {
this.setTooltipContent(this.getTooltipContent(countryID));
this.showTooltip(e);
countryElement.setAttribute('data-tooltip-open', 'true');
}
}.bind(this),
{ passive: true }
);
}.bind(this)
);
// Hide tooltip on touch outside
document.addEventListener(
'touchend',
function (e) {
if (
e.target.closest('.svgMap-country') ||
e.target.closest('.svgMap-tooltip')
) {
return;
}
this.hideTooltip();
var openTooltips = document.querySelectorAll(
'[data-tooltip-open="true"]'
);
openTooltips.forEach(function (element) {
element.setAttribute('data-tooltip-open', 'false');
});
var activeCountries = document.querySelectorAll('.svgMap-active');
activeCountries.forEach(function (element) {
element.classList.remove('svgMap-active');
});
}.bind(this),
{ passive: true }
);
// Hide tooltip on click outside
document.addEventListener(
'click',
function (e) {
if (
e.target.closest('.svgMap-country') ||
e.target.closest('.svgMap-tooltip')
) {
return;
}
this.hideTooltip();
var openTooltips = document.querySelectorAll(
'[data-tooltip-open="true"]'
);
openTooltips.forEach(function (element) {
element.setAttribute('data-tooltip-open', 'false');
});
var activeCountries = document.querySelectorAll('.svgMap-active');
activeCountries.forEach(function (element) {
element.classList.remove('svgMap-active');
});
}.bind(this),
{ passive: true }
);
// Expose instance
var me = this;
// Init pan zoom
this.mapPanZoom = svgPanZoom(this.mapImage, {
zoomEnabled: this.options.allowInteraction,
panEnabled: this.options.allowInteraction,
fit: true,
center: true,
minZoom: this.options.minZoom,
maxZoom: this.options.maxZoom,
zoomScaleSensitivity: this.options.zoomScaleSensitivity,
controlIconsEnabled: false,
dblClickZoomEnabled: this.options.allowInteraction
? this.options.dblClickZoomEnabled
: false,
mouseWheelZoomEnabled: this.options.allowInteraction
? this.options.mouseWheelZoomEnabled
: false,
preventMouseEventsDefault: true,
onZoom: function () {
me.setControlStatuses();
},
beforePan: function (oldPan, newPan) {
var gutterWidth = me.mapWrapper.offsetWidth * 0.85;
var gutterHeight = me.mapWrapper.offsetHeight * 0.85;
var sizes = this.getSizes();
var leftLimit =
-((sizes.viewBox.x + sizes.viewBox.width) * sizes.realZoom) +
gutterWidth;
var rightLimit =
sizes.width - gutterWidth - sizes.viewBox.x * sizes.realZoom;
var topLimit =
-((sizes.viewBox.y + sizes.viewBox.height) * sizes.realZoom) +
gutterHeight;
var bottomLimit =
sizes.height - gutterHeight - sizes.viewBox.y * sizes.realZoom;
return {
x: Math.max(leftLimit, Math.min(rightLimit, newPan.x)),
y: Math.max(topLimit, Math.min(bottomLimit, newPan.y))
};
}
});
if (this.options.initialPan.x != 0 || this.options.initialPan.y != 0) {
// Init zoom and pan
this.mapPanZoom.zoomAtPointBy(this.options.initialZoom, {
x: this.options.initialPan.x,
y: this.options.initialPan.y
});
} else {
// Init zoom
this.mapPanZoom.zoom(this.options.initialZoom);
}
// Initial zoom statuses
this.setControlStatuses();
// Zoom reset on resize
if (this.options.resetZoomOnResize) {
const resizeObserver = new ResizeObserver(() => this.mapReset());
resizeObserver.observe(this.mapWrapper);
}
}
// Create the tooltip content
getTooltipContent(countryID) {
// Custom tooltip
if (this.options.onGetTooltip) {
var customDiv = this.options.onGetTooltip(
this.tooltip,
countryID,
this.options.data.values[countryID]
);
if (customDiv) {
return customDiv;
}
}
var tooltipContentWrapper = this.createElement(
'div',
'svgMap-tooltip-content-container'
);
if (this.options.hideFlag === false) {
// Flag
var flagContainer = this.createElement(
'div',
'svgMap-tooltip-flag-container svgMap-tooltip-flag-container-' +
this.options.flagType,
tooltipContentWrapper
);
if (this.options.flagType === 'image') {
this.createElement(
'img',
'svgMap-tooltip-flag',
flagContainer
).setAttribute(
'src',
this.options.flagURL.replace('{0}', countryID.toLowerCase())
);
} else if (this.options.flagType === 'emoji') {
flagContainer.innerHTML = this.emojiFlags[countryID];
}
}
// Title
this.createElement(
'div',
'svgMap-tooltip-title',
tooltipContentWrapper
).innerHTML = this.getCountryName(countryID);
// Content
var tooltipContent = this.createElement(
'div',
'svgMap-tooltip-content',
tooltipContentWrapper
);
if (!this.options.data.values[countryID]) {
this.createElement(
'div',
'svgMap-tooltip-no-data',
tooltipContent
).innerHTML = this.options.noDataText;
} else {
var tooltipContentTable = '<table>';
Object.keys(this.options.data.data).forEach(
function (key) {
var item = this.options.data.data[key];
var value = this.options.data.values[countryID][key];
if (
(value !== undefined && this.options.hideMissingData === true) ||
this.options.hideMissingData === false
) {
item.floatingNumbers && (value = value.toFixed(1));
item.thousandSeparator &&
(value = this.numberWithCommas(value, item.thousandSeparator));
value = item.format
? item.format.replace('{0}', '<span>' + value + '</span>')
: '<span>' + value + '</span>';
tooltipContentTable +=
'<tr><td>' +
(item.name || '') +
'</td><td>' +
value +
'</td></tr>';
}
}.bind(this)
);
tooltipContentTable += '</table>';
tooltipContent.innerHTML = tooltipContentTable;
}
return tooltipContentWrapper;
}
// Set the disabled statuses for buttons
setControlStatuses() {
this.zoomControlIn.classList.remove('svgMap-disabled');
this.zoomControlIn.setAttribute('aria-disabled', 'false');
this.zoomControlOut.classList.remove('svgMap-disabled');
this.zoomControlOut.setAttribute('aria-disabled', 'false');
if (this.options.showZoomReset) {
this.zoomControlReset.classList.remove('svgMap-disabled');
this.zoomControlReset.setAttribute('aria-disabled', 'false');
}
if (this.mapPanZoom.getZoom().toFixed(3) <= this.options.minZoom) {
this.zoomControlOut.classList.add('svgMap-disabled');
this.zoomControlOut.setAttribute('aria-disabled', 'true');
}
if (this.mapPanZoom.getZoom().toFixed(3) >= this.options.maxZoom) {
this.zoomControlIn.classList.add('svgMap-disabled');
this.zoomControlIn.setAttribute('aria-disabled', 'true');
}
if (
this.options.showZoomReset &&
this.mapPanZoom.getZoom().toFixed(3) == this.options.initialZoom
) {
this.zoomControlReset.classList.add('svgMap-disabled');
this.zoomControlReset.setAttribute('aria-disabled', 'true');
}
}
// Zoom map
zoomMap(direction) {
if (
this[
'zoomControl' + direction.charAt(0).toUpperCase() + direction.slice(1)
].classList.contains('svgMap-disabled')
) {
return false;
}
if (direction === 'reset') {
this.mapPanZoom.reset();
if (this.options.initialPan.x != 0 || this.options.initialPan.y != 0) {
// Init zoom and pan
this.mapPanZoom.zoomAtPointBy(this.options.initialZoom, {
x: this.options.initialPan.x,
y: this.options.initialPan.y
});
} else {
// Init zoom
this.mapPanZoom.zoom(this.options.initialZoom);
}
} else {
this.mapPanZoom[direction == 'in' ? 'zoomIn' : 'zoomOut']();
}
}
// Zoom reset
mapReset() {
this.mapPanZoom.resize();
this.zoomMap('reset');
}
// Zoom to Contient
zoomContinent(contientIso) {
const continent = this.continents[contientIso];
if (continent.iso == 'EA') this.mapPanZoom.reset();
else if (continent.pan) {
this.mapPanZoom.reset();
this.mapPanZoom.zoomAtPoint(continent.zoom, continent.pan);
}
}
// Add elements to show the zoom with keys notice
addMouseWheelZoomNotice() {
var noticeWrapper = document.createElement('div');
noticeWrapper.classList.add('svgMap-block-zoom-notice');
var noticeContainer = document.createElement('div');
noticeContainer.innerHTML =
navigator.appVersion.indexOf('Mac') != -1
? this.options.mouseWheelKeyMessageMac
: this.options.mouseWheelKeyMessage;
noticeWrapper.append(noticeContainer);
this.wrapper.append(noticeWrapper);
}
// Show the zoom with keys notice
showMouseWheelZoomNotice(duration) {
if (this.mouseWheelNoticeJustHidden) {
return;
}
this.autoHideMouseWheelNoticeTimeout &&
clearTimeout(this.autoHideMouseWheelNoticeTimeout);
this.autoHideMouseWheelNoticeTimeout = setTimeout(
function () {
this.hideMouseWheelZoomNotice();
}.bind(this),
duration || 2400
);
this.wrapper.classList.add('svgMap-block-zoom-notice-active');
}
// Hide the zoom with keys notice
hideMouseWheelZoomNotice() {
this.wrapper.classList.remove('svgMap-block-zoom-notice-active');
this.autoHideMouseWheelNoticeTimeout &&
clearTimeout(this.autoHideMouseWheelNoticeTimeout);
}
// Block shing the zoom wheel notice for some time
blockMouseWheelZoomNotice(duration) {
this.mouseWheelNoticeJustHidden = true;
this.mouseWheelNoticeJustHiddenTimeout &&
clearTimeout(this.mouseWheelNoticeJustHiddenTimeout);
this.mouseWheelNoticeJustHiddenTimeout = setTimeout(
function () {
this.mouseWheelNoticeJustHidden = false;
}.bind(this),
duration || 600
);
}
// Add the events when you are only allowed to scrool with a key pressed
addMouseWheelZoomWithKeyEvents() {
// Add events to wrapper
this.wrapper.addEventListener(
'wheel',
function (ev) {
if (!document.body.classList.contains('svgMap-zoom-key-pressed')) {
this.showMouseWheelZoomNotice();
} else {
this.hideMouseWheelZoomNotice();
this.blockMouseWheelZoomNotice();
}
}.bind(this),
{
passive: true
}
);
// Add with keydown
document.addEventListener(
'keydown',
function (ev) {
if (
ev.key == 'Alt' ||
ev.key == 'Control' ||
ev.key == 'Meta' ||
ev.key == 'Shift'
) {
document.body.classList.add('svgMap-zoom-key-pressed');
this.hideMouseWheelZoomNotice();
this.blockMouseWheelZoomNotice();
}
}.bind(this)
);
// Fallback with wheel as sometimes it wont trigger when window is out of focus
this.wrapper.addEventListener('wheel', function (ev) {
if (ev.altKey || ev.ctrlKey || ev.metaKey || ev.shiftKey) {
document.body.classList.add('svgMap-zoom-key-pressed');
// TODO wont be removed when window out of focus
}
});
// Only add following events to the document once
if (document.body.classList.contains('svgMap-key-events-added')) {
return false;
}
document.body.classList.add('svgMap-key-events-added');
// Remove with keyup
document.addEventListener('keyup', function (ev) {
if (
ev.key == 'Alt' ||
ev.key == 'Control' ||
ev.key == 'Meta' ||
ev.key == 'Shift'
) {
document.body.classList.remove('svgMap-zoom-key-pressed');
}
});
}
// Map paths
mapPaths = {
'AF': {
d: 'M1369.9,333.8h-5.4l-3.8-0.5l-2.5,2.9l-2.1,0.7l-1.5,1.3l-2.6-2.1l-1-5.4l-1.6-0.3v-2l-3.2-1.5l-1.7,2.3l0.2,2.6 l-0.6,0.9l-3.2-0.1l-0.9,3l-2.1-1.3l-3.3,2.1l-1.8-0.8l-4.3-1.4h-2.9l-1.6-0.2l-2.9-1.7l-0.3,2.3l-4.1,1.2l0.1,5.2l-2.5,2l-4,0.9 l-0.4,3l-3.9,0.8l-5.9-2.4l-0.5,8l-0.5,4.7l2.5,0.9l-1.6,3.5l2.7,5.1l1.1,4l4.3,1.1l1.1,4l-3.9,5.8l9.6,3.2l5.3-0.9l3.3,0.8l0.9-1.4 l3.8,0.5l6.6-2.6l-0.8-5.4l2.3-3.6h4l0.2-1.7l4-0.9l2.1,0.6l1.7-1.8l-1.1-3.8l1.5-3.8l3-1.6l-3-4.2l5.1,0.2l0.9-2.3l-0.8-2.5l2-2.7 l-1.4-3.2l-1.9-2.8l2.4-2.8l5.3-1.3l5.8-0.8l2.4-1.2l2.8-0.7L1369.9,333.8L1369.9,333.8z'
},
'AL': {
d: 'M1077.5,300.5l-2,3.1l0.5,1.9l0,0l1,1l-0.5,1.9l-0.1,4.3l0.7,3l3,2.1l0.2,1.4l1,0.4l2.1-3l0.1-2.1l1.6-0.9V312 l-2.3-1.6l-0.9-2.6l0.4-2.1l0,0l-0.5-2.3l-1.3-0.6l-1.3-1.6l-1.3,0.5L1077.5,300.5L1077.5,300.5z'
},
'DZ': {
d: 'M1021,336.9l-3.6,0.4l-2.2-1.5h-5.6l-4.9,2.6l-2.7-1l-8.7,0.5l-8.9,1.2l-5,2l-3.4,2.6l-5.7,1.2l-5.1,3.5l2,4.1 l0.3,3.9l1.8,6.7l1.4,1.4l-1,2.5l-7,1l-2.5,2.4l-3.1,0.5l-0.3,4.7l-6.3,2.5l-2.1,3.2L944,383l-5.4,1l-8.9,4.7l-0.1,7.5v0.4l-0.1,1.2 l20.3,15.5l18.4,13.9l18.6,13.8l1.3,3l3.4,1.8l2.6,1.1l0.1,4l6.1-0.6l7.8-2.8l15.8-12.5l18.6-12.2l-2.5-4l-4.3-2.9l-2.6,1.2l-2-3.6 l-0.2-2.7l-3.4-4.7l2.1-2.6l-0.5-4l0.6-3.5l-0.5-2.9l0.9-5.2l-0.4-3l-1.9-5.6l-2.6-11.3l-3.4-2.6v-1.5l-4.5-3.8l-0.6-4.8l3.2-3.6 l1.1-5.3l-1-6.2L1021,336.9L1021,336.9z'
},
'AD': {
d: 'M985.4,301.7l0.2-0.4l-0.2-0.2l-0.7-0.2l-0.3-0.1l-0.4,0.3l-0.1,0.3l0.1,0.1v0.4l0.1,0.2h0.4L985.4,301.7 L985.4,301.7z'
},
'AO': {
d: 'M1068.3,609.6l-16.6-0.1l-1.9,0.7l-1.7-0.1l-2.3,0.9l-0.5,1.2l2.8,4l1.1,4.3l1.6,6.1l-1.7,2.6l-0.3,1.3l1.3,3.8 l1.5,3.9l1.6,2.2l0.3,3.6l-0.7,4.8l-1.8,2.8l-3.3,4.2l-1.3,2.6l-1.9,5.7l-0.3,2.7l-2,5.9l-0.9,5.5l0.5,4l2.7-1.2l3.3-1l3.6,0.1 l3.2,2.9l0.9-0.4l22.5-0.3l3.7,3l13.4,0.9l10.3-2.5l-3.5-4l-3.6-5.2l0.8-20.3l11.6,0.1l-0.5-2.2l0.9-2.4l-0.9-3l0.7-3l-0.5-2 l-2.6-0.4l-3.5,1l-2.4-0.2l-1.4,0.6l0.5-7.6l-1.9-2.3l-0.3-4l0.9-3.8l-1.2-2.4v-4h-6.8l0.5-2.3h-2.9l-0.3,1.1l-3.4,0.3l-1.5,3.7 l-0.9,1.6l-3-0.9l-1.9,0.9l-3.7,0.5l-2.1-3.3l-1.3-2.1l-1.6-3.8L1068.3,609.6L1068.3,609.6z M1046.5,608.3l0.2-2.7l0.9-1.7l2-1.3 l-2-2.2l-1.8,1.1l-2.2,2.7l1.4,4.8L1046.5,608.3L1046.5,608.3z'
},
'AI': {
d: 'M627.9,456.2l0.1-0.2l-0.2-0.1l-0.8,0.5v0.1L627.9,456.2z'
},
'AG': {
d: 'M634.3,463.8l0.2-0.1v-0.1v-0.2l-0.1-0.1l-0.1-0.2l-0.4-0.2l-0.5,0.5v0.2l0.1,0.3l0.6,0.1L634.3,463.8L634.3,463.8z M634.5,460.3v-0.5l-0.1-0.2h-0.3l-0.1-0.1h-0.1l-0.1,0.1l0.1,0.6l0.5,0.3L634.5,460.3L634.5,460.3z'
},
'AR': {
d: 'M669.8,920.7l0.9-3l-7.3-1.5l-7.7-3.6l-4.3-4.6l-3-2.8l5.9,13.5h5l2.9,0.2l3.3,2.1L669.8,920.7L669.8,920.7z M619.4,712.6l-7.4-1.5l-4,5.7l0.9,1.6l-1.1,6.6l-5.6,3.2l1.6,10.6l-0.9,2l2,2.5l-3.2,4l-2.6,5.9l-0.9,5.8l1.7,6.2l-2.1,6.5 l4.9,10.9l1.6,1.2l1.3,5.9l-1.6,6.2l1.4,5.4l-2.9,4.3l1.5,5.9l3.3,6.3l-2.5,2.4l0.3,5.7l0.7,6.4l3.3,7.6l-1.6,1.2l3.6,7.1l3.1,2.3 l-0.8,2.6l2.8,1.3l1.3,2.3l-1.8,1.1l1.8,3.7l1.1,8.2l-0.7,5.3l1.8,3.2l-0.1,3.9l-2.7,2.7l3.1,6.6l2.6,2.2l3.1-0.4l1.8,4.6l3.5,3.6 l12,0.8l4.8,0.9l2.2,0.4l-4.7-3.6l-4.1-6.3l0.9-2.9l3.5-2.5l0.5-7.2l4.7-3.5l-0.2-5.6l-5.2-1.3l-6.4-4.5l-0.1-4.7l2.9-3.1l4.7-0.1 l0.2-3.3l-1.2-6.1l2.9-3.9l4.1-1.9l-2.5-3.2l-2.2,2l-4-1.9l-2.5-6.2l1.5-1.6l5.6,2.3l5-0.9l2.5-2.2l-1.8-3.1l-0.1-4.8l-2-3.8 l5.8,0.6l10.2-1.3l6.9-3.4l3.3-8.3l-0.3-3.2l-3.9-2.8l-0.1-4.5l-7.8-5.5l-0.3-3.3l-0.4-4.2l0.9-1.4l-1.1-6.3l0.3-6.5l0.5-5.1 l5.9-8.6l5.3-6.2l3.3-2.6l4.2-3.5l-0.5-5.1l-3.1-3.7l-2.6,1.2l-0.3,5.7l-4.3,4.8l-4.2,1.1l-6.2-1l-5.7-1.8l4.2-9.6l-1.1-2.8 l-5.9-2.5l-7.2-4.7l-4.6-1L632,713.7l-1-1.3l-6.3-0.3l-1.6,5.1L619.4,712.6L619.4,712.6z'
},
'AM': {
d: 'M1219,325.1l-0.9-4.4l-2.5-1.1l-2.5-1.7l1-2l-3.1-2.2l0.7-1.5l-2.2-1.1l-1.4-1.7l-6.9,1l1.3,2.2v3.1l4.2,1.5 l2.4,1.9l1-0.2l1.8,1.7h2.3l0.2,1l2.8,3.7L1219,325.1L1219,325.1z'
},
'AW': {
d: 'M586.6,492.9l-0.1-0.1l-0.3-0.6l-0.3-0.3l-0.1,0.1l-0.1,0.3l0.3,0.3l0.3,0.4l0.3,0.1L586.6,492.9L586.6,492.9z'
},
'AU': {
d: 'M1726.7,832l-3-0.5l-1.9,2.9l-0.6,5.4l-2.1,4l-0.5,5.3l3,0.2l0.8,0.3l6.6-4.3l0.6,1.7l4-4.9l3.2-2.2l4.5-7.3 l-2.8-0.5l-4.8,1.2l-3.4,0.9L1726.7,832L1726.7,832z M1776.8,659.7l0.5-2.3l0.1-3.6l-1.6-3.2l0.1-2.7l-1.3-0.8l0.1-3.9l-1.2-3.2 l-2.3,2.4l-0.4,1.8l-1.5,3.5l-1.8,3.4l0.6,2.1l-1.2,1.3l-1.5,4.8l0.1,3.7l-0.7,1.8l0.3,3.1l-2.6,5l-1.3,3.5l-1.7,2.9l-1.7,3.4 l-4.1,2.1l-4.9-2.1l-0.5-2l-2.5-1.6h-1.6l-3.3-3.8l-2.5-2.2l-3.9-2l-3.9-3.5l-0.1-1.8l2.5-3.1l2.1-3.2l-0.3-2.6l1.9-0.2l2.5-2.5 l2-3.4l-2.2-3.2l-1.5,1.2l-2-0.5l-3.5,1.8l-3.2-2l-1.7,0.7l-4.5-1.6l-2.7-2.7l-3.5-1.5l-3.1,0.9l3.9,2.1l-0.3,3.2l-4.8,1.2l-2.8-0.7 l-3.6,2.2l-2.9,3.7l0.6,1.5l-2.7,1.7l-3.4,5.1l0.6,3.5l-3.4-0.6h-3.5l-2.5-3.8l-3.7-2.9l-2.8,0.8l-2.6,0.9l-0.3,1.6l-2.4-0.7 l-0.3,1.8l-3,1.1l-1.7,2.5l-3.5,3.1l-1.4,4.8l-2.3-1.3l-2.2,3.1l1.5,3l-2.6,1.2l-1.4-5.5l-4.8,5.4l-0.8,3.5l-0.7,2.5l-3.8,3.3 l-2,3.4l-3.5,2.8l-6.1,1.9l-3.1-0.2l-1.5,0.6l-1.1,1.4l-3.5,0.7l-4.7,2.4l-1.4-0.8l-2.6,0.5l-4.6,2.3l-3.2,2.7l-4.8,2.1l-3.1,4.4 l0.4-4.8l-3.1,4.6l-0.1,3.7l-1.3,3.2l-1.5,1.5l-1.3,3.7l0.9,1.9l0.1,2l1.6,5l-0.7,3.3l-1-2.5l-2.3-1.8l0.4,5.9l-1.7-2.8l0.1,2.8 l1.8,5l-0.6,5l1.7,2.5l-0.4,1.9l0.9,4.1l-1.3,3.6l-0.3,3.6l0.7,6.5l-0.7,3.7l-2.2,4.4l-0.6,2.3l-1.5,1.5l-2.9,0.8l-1.5,3.7l2.4,1.2 l4,4.1h3.6l3.8,0.3l3.3-2.1l3.4-1.8l1.4,0.3l4.5-3.4l3.8-0.3l4.1-0.7l4.2,1.2l3.6-0.6l4.6-0.2l3-2.6l2.3-3.3l5.2-1.5l6.9-3.2l5,0.4 l6.9-2.1l7.8-2.3l9.8-0.6l4,3.1l3.7,0.2l5.3,3.8l-1.6,1.5l1.8,2.4l1.3,4.6l-1.6,3.4l2.9,2.6l4.3-5.1l4.3-2.1l6.7-5.5l-1.6,4.7 l-3.4,3.2l-2.5,3.7l-4.4,3.5l5.2-1.2l4.7-4.4l-0.9,4.8l-3.2,3.1l4.7,0.8l1.3,2.6l-0.4,3.3l-1.5,4.9l1.4,4l4,1.9l2.8,0.4l2.4,1 l3.5,1.8l7.2-4.7l3.5-1.2l-2.7,3.4l2.6,1.1l2.7,2.8l4.7-2.7l3.8-2.5l6.3-2.7l6-0.2l4.2-2.3l0.9-2l3-4.5l3.9-4.8l3.6-3.2l4.4-5.6 l3.3-3.1l4.4-5l5.4-3.1l5-5.8l3.1-4.5l1.4-3.6l3.8-5.7l2.1-2.9l2.5-5.7l-0.7-5.4l1.7-3.9l1.1-3.7v-5.1l-2.8-5.1l-1.9-2.5l-2.9-3.9 l0.7-6.7l-1.5,1l-1.6-2.8l-2.5,1.4l-0.6-6.9l-2.2-4l1-1.5l-3.1-2.8l-3.2-3l-5.3-3.3l-0.9-4.3l1.3-3.3l-0.4-5.5l-1.3-0.7l-0.2-3.2 l-0.2-5.5l1.1-2.8l-2.3-2.5l-1.4-2.7l-3.9,2.4L1776.8,659.7L1776.8,659.7z'
},
'AT': {
d: 'M1060.2,264l-2.3-1.2l-2.3,0.3l-4-1.9l-1.7,0.5l-2.6,2.5l-3.8-2l-1.5,2.9l-1.7,0.8l1,4l-0.4,1.1l-1.7-1.3l-2.4-0.2 l-3.4,1.2l-4.4-0.3l-0.6,1.6l-2.6-1.7l-1.5,0.3l0.2,1.1l-0.7,1.6l2.3,1.1l2.6,0.2l3.1,0.9l0.5-1.2l4.8-1.1l1.3,2.2l7.2,1.6l4.2,0.4 l2.4-1.4l4.3-0.1l0.9-1.1l1.3-4l-1.1-1.3h2.8l0.2-2.6l-0.7-2.1L1060.2,264L1060.2,264z'
},
'AZ': {
d: 'M1210.1,318.9l-1,0.2l1.2,2.4l3.2,2.9l3.7,0.9l-2.8-3.7l-0.2-1h-2.3L1210.1,318.9L1210.1,318.9z M1220.5,309.6 l-4.3-3.8l-1.5-0.2l-1.1,0.9l3.2,3.4l-0.6,0.7l-2.8-0.4l-4.2-1.8l-1.1,1l1.4,1.7l2.2,1.1l-0.7,1.5l3.1,2.2l-1,2l2.5,1.7l2.5,1.1 l0.9,4.4l5.3-4.7l1.9-0.5l1.9,1.9l-1.2,3.1l3.8,3.4l1.3-0.3l-0.8-3.2l1.7-1.5l0.4-2.2l-0.1-5l4.2-0.5l-2-1.7l-2.5-0.2l-3.5-4.5 l-3.4-3.2l0,0l-2.6,2.5l-0.5,1.5L1220.5,309.6L1220.5,309.6z'
},
'BS': {
d: 'M574.4,437.3l0.2-0.6l-0.3-0.1l-0.5,0.7l-0.6,0.3h-0.3l-0.7-0.3h-0.5l-0.4,0.5l-0.6,0.1l0.1,0.1v0.2l-0.2,0.3v0.2 l0.1,0.3l1.5-0.1l1.3-0.2l0.7-0.9L574.4,437.3z M575.2,435.3l-0.4-0.3l-0.4,0.3l0.1,0.3L575.2,435.3L575.2,435.3z M575.2,429.5 l-0.4-0.2l-0.3,0.5l0.3,0.1l0.7-0.1l0.5,0.1l0.5,0.4l0.3-0.2l-0.1-0.1l-0.4-0.3l-0.6-0.1h-0.2L575.2,429.5L575.2,429.5z M568.6,430.8l0.7-0.6l0.7-0.3l0.9-1.1l-0.1-0.9l0.2-0.4l-0.6,0.1l-0.1,0.3l-0.1,0.3l0.3,0.4v0.2l-0.2,0.4l-0.3,0.1l-0.1,0.2 l-0.3,0.1l-0.4,0.5l-0.8,0.6l-0.2,0.3L568.6,430.8L568.6,430.8z M569.8,427.6l-0.6-0.2L569,427l-0.4-0.1l-0.1,0.2v0.2l0.1,0.4 l0.2-0.1l0.8,0.4l0.4-0.3L569.8,427.6z M565.7,426.5v-0.7l-0.4-0.5l-0.6-0.4l-0.1-1.2l-0.3-0.7l-0.2-0.6l-0.4-0.8v0.5l0.1,0.1 l0.1,0.6l0.4,0.9l0.1,0.4l-0.1,0.4l-0.4,0.1l-0.1,0.2l0.5,0.3l0.8,0.3l0.5,1.3L565.7,426.5L565.7,426.5z M561.6,423l-0.5-0.3 l-0.2-0.3l-0.7-0.7l-0.3-0.1l-0.2,0.4l0.4,0.1l0.9,0.7l0.4,0.2L561.6,423L561.6,423z M568.9,419l-0.1-0.3h-0.1l-0.3,0.1l-0.3,0.9 h0.3L568.9,419L568.9,419z M551.3,417.9l-0.2-0.3l-0.3,0.2h-0.5l-0.2,0.1h-0.4l-0.3,0.2l0.4,0.8l0.3,0.3l0.1,1l0.2,0.1l-0.1,0.7 l1.1,0.1l0.4-0.8V420v-0.1v-0.2v-0.2v-0.9l-0.3-0.5l-0.4,0.6l-0.4-0.3l0.6-0.4L551.3,417.9L551.3,417.9z M564.2,418.2l-1-1.4v-0.2 l-0.5-1.5l-0.3-0.1l-0.1,0.1l-0.1,0.2l0.4,0.4v0.4l0.3,0.2l0.4,1.1l0.4,0.4l-0.1,0.3l-0.4,0.3l-0.1,0.2h0.1l0.6-0.1h0.4L564.2,418.2 L564.2,418.2z M553.7,413l0.5-0.2l0,0l-0.3-0.2h-0.7l-0.4,0.1l-0.2,0.2l0.1,0.1l0.4,0.1L553.7,413L553.7,413z M551.3,415l-0.5-0.6 l-0.3-0.9l-0.2-0.4l0.1-0.5l-0.3-0.4l-0.6-0.4l-0.3,0.1l0.1,1.1l-0.2,0.6l-0.8,1.1l0.1,0.4l0,0l0.1,0.2l-0.5,0.4v-0.3l-0.6,0.1 l0.3,0.5l0.6,0.4l0.3,0.1l0.3-0.2v0.5l0.3,0.4l0.1,0.4l0.3-0.3l0.6-0.2l0.2-0.2l0.7-0.4v-0.2l0.1-0.6L551.3,415L551.3,415z M558,410 l-0.3-0.5l-0.1,0.1l-0.1,0.4l-0.3,0.4l0.5-0.1l0.4,0.1l0.6,0.5l0.7,0.2l0.3,0.6l0.6,0.6v0.6l-0.4,0.6l-0.1,0.7l-0.6,0.1l0.1,0.1 l0.3,0.3l0.1,0.4l0.2,0.2v-0.7l0.3-0.8l0.4-1.3l-0.1-0.3l-0.3-0.3l-0.7-0.9l-0.7-0.3L558,410L558,410z M549.2,402.1l-0.5-0.4 l-0.2,0.4v0.1l-0.1,0.3l-0.5,0.4l-0.5,0.1l-0.7-0.6l-0.2-0.1l0.8,1.1l0.3,0.1h0.4l0.9-0.3l1.6-0.5l1.7-0.2l0.1-0.2l-0.1-0.3 l-0.8,0.2l-1-0.1l-0.2,0.2h-0.4L549.2,402.1z M555.3,407.3l0.2-0.3l0.4-1.8l0.8-0.6l0.1-1.2l-0.5-0.5l-0.4-0.2l-0.1-0.2l0.1-0.2 l-0.2-0.1l-0.3-0.2l-0.4-0.6l-0.4-0.4l-0.7-0.1l-0.6-0.1l-0.4-0.1l-0.5,0.3h0.8l1.5,0.3l0.7,1.5l0.5,0.4l0.1,0.4l-0.2,0.4v0.4 l-0.3,0.5l-0.1,0.8l-0.3,0.4l-0.7,0.5l0.4,0.2l0.3,0.6L555.3,407.3L555.3,407.3z'
},
'BH': {
d: 'M1253,408.3l0.7-3l-0.5-0.9l-1.6,1.2l0.6,0.9l-0.2,0.7L1253,408.3z'
},
'BD': {
d: 'M1486.5,431.9l-4.5-10.1l-1.5,0.1l-0.2,4l-3.5-3.3l1.1-3.6l2.4-0.4l1.6-5.3l-3.4-1.1l-5,0.1l-5.4-0.9l-1.2-4.4 l-2.7-0.4l-4.8-2.7l-1.2,4.3l4.6,3.4l-3.1,2.4l-0.8,2.3l3.7,1.7l-0.4,3.8l2.6,4.8l1.6,5.2l2.2,0.6l1.7,0.7l0.6-1.2l2.5,1.3l1.3-3.5 l-0.9-2.6l5.1,0.2l2.8,3.7l1.5,3.1l0.8,3.2l2,3.3l-1.1-5.1l2.1,1L1486.5,431.9L1486.5,431.9z'
},
'BB': {
d: 'M644.9,488.9l0.4-0.4l-0.3-0.3l-0.6-0.8l-0.3,0.1v1l0.1,0.3l0.5,0.3L644.9,488.9L644.9,488.9z'
},
'BY': {
d: 'M1112.8,219.4l-5.2-1.5l-4.6,2.3l-2.6,1l0.9,2.6l-3.5,2l-0.5,3.4l-4.8,2.2h-4.6l0.6,2.7l1.7,2.3l0.3,2.4l-2.7,1.2 l1.9,2.9l0.5,2.7l2.2-0.3l2.4-1.6l3.7-0.2l5,0.5l5.6,1.5l3.8,0.1l2,0.9l1.6-1.1l1.5,1.5l4.3-0.3l2,0.6l-0.2-3.1l1.2-1.4l4.1-0.3l0,0 l-2-3.9l-1.5-2l0.8-0.6l3.9,0.2l1.6-1.3l-1.7-1.6l-3.4-1.1l0.1-1.1l-2.2-1.1l-3.7-3.9l0.6-1.6l-1-2.9l-4.8-1.4l-2.3,0.7 L1112.8,219.4L1112.8,219.4z'
},
'BE': {
d: 'M1000.7,246.2l-4.4,1.3l-3.6-0.5l0,0l-3.8,1.2l0.7,2.2l2.2,0.1l2.4,2.4l3.4,2.9l2.5-0.4l4.4,2.8l0.4-3.5l1.3-0.2 l0.4-4.2l-2.8-1.4L1000.7,246.2L1000.7,246.2z'
},
'BZ': {
d: 'M482.5,471.1l1.4-2.2l1-0.2l1.3-1.7l1-3.2l-0.3-0.6l0.9-2.3l-0.4-1l1.3-2.7l0.3-1.8h-1.1l0.1-0.9h-1l-2.5,3.9 l-0.9-0.8l-0.7,0.3l-0.1,1l-0.7,5l-1.2,7.2L482.5,471.1L482.5,471.1z'
},
'BJ': {
d: 'M996.9,498l-4.3-3.7h-2l-1.9,1.9l-1.2,1.9l-2.7,0.6l-1.2,2.8l-1.9,0.7l-0.7,3.3l1.7,1.9l2,2.3l0.2,3.1l1.1,1.3 l-0.2,14.6l1.4,4.4l4.6-0.8l0.3-10.2L992,518l1-4l1.7-1.9l2.7-4l-0.6-1.7l1.1-2.5l-1.2-3.8L996.9,498L996.9,498z'
},
'BM': {
d: 'M630.2,366.8l0.4-0.6h-0.1l-0.5,0.5l-0.6,0.2l0.1,0.1h0.1L630.2,366.8z'
},
'BT': {
d: 'M1474.7,395.5l-2.7-1.8l-2.9-0.1l-4.2-1.5l-2.6,1.6l-2.6,4.8l0.3,1.2l5.5,2.5l3.2-1l4.7,0.4l4.4-0.2l-0.4-3.9 L1474.7,395.5L1474.7,395.5z'
},
'BO': {
d: 'M655.7,700.5l1.6-1.3l-0.8-3.6l1.3-2.8l0.5-5l-1.6-4l-3.2-1.7l-0.8-2.6l0.6-3.6l-10.7-0.3l-2.7-7.4l1.6-0.1 l-0.3-2.8l-1.2-1.8l-0.5-3.7l-3.3-1.9l-3.5,0.1l-2.5-1.9l-3.8-1.2l-2.4-2.4l-6.3-1l-6.4-5.7l0.3-4.3l-0.9-2.5l0.4-4.7l-7.3,1.1 l-2.8,2.3l-4.8,2.6l-1.1,1.9l-2.9,0.2l-4.2-0.6l5.5,10.3l-1.1,2.1l0.1,4.5l0.3,5.4l-1.9,3.2l1.2,2.4l-1.1,2.1l2.8,5.3L591,684 l3.1,4.3l1.2,4.6l3.2,2.7l-1.1,6.2l3.7,7.1l3.1,8.8l3.8-0.9l4-5.7l7.4,1.5l3.7,4.6l1.6-5.1l6.3,0.3l1,1.3l1.5-7.6l-0.2-3.4l2.1-5.6 l9.5-1.9l5.1,0.1l5.4,3.3L655.7,700.5L655.7,700.5z'
},
'BA': {
d: 'M1062.2,284.9l-2.3,0.1l-1,1.3l-1.9-1.4l-0.9,2.5l2.7,2.9l1.3,1.9l2.5,2.3l2,1.4l2.2,2.5l4.7,2.4