svgmap 2.20.0 → 2.21.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +22 -0
- package/dist/index.cjs +317 -179
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +317 -179
- package/dist/index.js.map +1 -1
- package/dist/svg-map.css +9 -0
- package/dist/svg-map.min.css +1 -1
- package/dist/svg-map.umd.js +317 -179
- package/dist/svg-map.umd.js.map +1 -1
- package/dist/svg-map.umd.min.js +1 -1
- package/dist/svgMap.css +9 -0
- package/dist/svgMap.js +317 -179
- package/dist/svgMap.js.map +1 -1
- package/dist/svgMap.min.css +1 -1
- package/dist/svgMap.min.js +1 -1
- package/package.json +4 -4
- package/src/js/core/svg-map.js +317 -179
- package/src/scss/map.scss +11 -0
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "svgmap",
|
|
3
3
|
"description": "svgMap is a JavaScript library that lets you easily create an interactable world map comparing customizable data for each country.",
|
|
4
|
-
"version": "2.
|
|
4
|
+
"version": "2.21.0",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
|
7
7
|
"keywords": [
|
|
@@ -43,12 +43,12 @@
|
|
|
43
43
|
"prepublishOnly": "npm run build && node test/assets.js"
|
|
44
44
|
},
|
|
45
45
|
"devDependencies": {
|
|
46
|
-
"@rollup/plugin-commonjs": "^29.0.
|
|
46
|
+
"@rollup/plugin-commonjs": "^29.0.3",
|
|
47
47
|
"@rollup/plugin-node-resolve": "^16.0.3",
|
|
48
48
|
"@rollup/plugin-terser": "^1.0.0",
|
|
49
49
|
"csso": "^5.0.5",
|
|
50
|
-
"rollup": "^4.
|
|
50
|
+
"rollup": "^4.61.0",
|
|
51
51
|
"rollup-plugin-postcss": "^4.0.2",
|
|
52
|
-
"sass": "^1.
|
|
52
|
+
"sass": "^1.100.0"
|
|
53
53
|
}
|
|
54
54
|
}
|
package/src/js/core/svg-map.js
CHANGED
|
@@ -101,7 +101,34 @@ export default class svgMap {
|
|
|
101
101
|
showContinentSelector: false,
|
|
102
102
|
|
|
103
103
|
// Reset zoom on resize
|
|
104
|
-
resetZoomOnResize: false
|
|
104
|
+
resetZoomOnResize: false,
|
|
105
|
+
|
|
106
|
+
// Static pins: false | string[] | function(countryID, countryValues) => boolean
|
|
107
|
+
staticPins: false,
|
|
108
|
+
|
|
109
|
+
// Default pin fill color
|
|
110
|
+
pinColor: '#000000',
|
|
111
|
+
|
|
112
|
+
// Default pin stroke color and width (circle pins; width is in screen pixels with non-scaling stroke)
|
|
113
|
+
pinStrokeColor: '#ffffff',
|
|
114
|
+
pinStrokeWidth: 1.5,
|
|
115
|
+
|
|
116
|
+
// Default pin radius in SVG units (viewBox is 2000 × 1001)
|
|
117
|
+
pinSize: 8,
|
|
118
|
+
|
|
119
|
+
// Custom pin element: function(countryID, countryValues) => SVGElement | null
|
|
120
|
+
onGetPin: null,
|
|
121
|
+
|
|
122
|
+
// Image URL to use as a pin instead of the default circle (can also be set per-country via values[id].pinImage)
|
|
123
|
+
pinImage: null,
|
|
124
|
+
|
|
125
|
+
// Width and height of the pin image in SVG units (viewBox is 2000 × 1001)
|
|
126
|
+
pinImageWidth: 20,
|
|
127
|
+
pinImageHeight: 20,
|
|
128
|
+
|
|
129
|
+
// Offset from computed pin position, in SVG units (added after auto center or pinX/pinY)
|
|
130
|
+
pinOffsetX: 0,
|
|
131
|
+
pinOffsetY: 0
|
|
105
132
|
};
|
|
106
133
|
|
|
107
134
|
this.options = Object.assign({}, defaultOptions, options);
|
|
@@ -953,6 +980,102 @@ export default class svgMap {
|
|
|
953
980
|
|
|
954
981
|
// Add map elements
|
|
955
982
|
var countryElements = [];
|
|
983
|
+
|
|
984
|
+
const clearActive = function clearActive() {
|
|
985
|
+
this.mapImage
|
|
986
|
+
.querySelectorAll('.svgMap-active')
|
|
987
|
+
.forEach((el) => el.classList.remove('svgMap-active'));
|
|
988
|
+
}.bind(this);
|
|
989
|
+
|
|
990
|
+
const isClickTooltip =
|
|
991
|
+
this.options.showTooltips && this.options.tooltipTrigger === 'click';
|
|
992
|
+
|
|
993
|
+
const getCountryFromEvent = function (e) {
|
|
994
|
+
return e.target && e.target.closest
|
|
995
|
+
? e.target.closest('.svgMap-country')
|
|
996
|
+
: null;
|
|
997
|
+
};
|
|
998
|
+
|
|
999
|
+
const raiseCountry = function (countryElement, setActive) {
|
|
1000
|
+
if (setActive) {
|
|
1001
|
+
clearActive();
|
|
1002
|
+
}
|
|
1003
|
+
countryElement.parentNode.insertBefore(
|
|
1004
|
+
countryElement,
|
|
1005
|
+
this.persistentTooltipGroup || this.pinGroup || null
|
|
1006
|
+
);
|
|
1007
|
+
if (setActive) {
|
|
1008
|
+
countryElement.classList.add('svgMap-active');
|
|
1009
|
+
}
|
|
1010
|
+
}.bind(this);
|
|
1011
|
+
|
|
1012
|
+
const showCountryTooltip = function (countryElement, e, setActive) {
|
|
1013
|
+
raiseCountry(countryElement, setActive);
|
|
1014
|
+
this.setTooltipContent(this.getTooltipContent(countryElement.dataset.id));
|
|
1015
|
+
this.showTooltip(e);
|
|
1016
|
+
}.bind(this);
|
|
1017
|
+
|
|
1018
|
+
// Touch only: preview tooltip on finger down without marking country active
|
|
1019
|
+
// (active is set on pointerup so link countries keep two-tap navigation)
|
|
1020
|
+
this.mapImage.addEventListener(
|
|
1021
|
+
'pointerdown',
|
|
1022
|
+
(e) => {
|
|
1023
|
+
if (!this.options.showTooltips || e.pointerType !== 'touch') {
|
|
1024
|
+
return;
|
|
1025
|
+
}
|
|
1026
|
+
|
|
1027
|
+
const countryElement = getCountryFromEvent(e);
|
|
1028
|
+
if (!countryElement) {
|
|
1029
|
+
this.hideTooltip();
|
|
1030
|
+
return;
|
|
1031
|
+
}
|
|
1032
|
+
|
|
1033
|
+
showCountryTooltip(countryElement, e, false);
|
|
1034
|
+
this.moveTooltip(e);
|
|
1035
|
+
},
|
|
1036
|
+
{ passive: true }
|
|
1037
|
+
);
|
|
1038
|
+
|
|
1039
|
+
this.mapImage.addEventListener(
|
|
1040
|
+
'pointercancel',
|
|
1041
|
+
(e) => {
|
|
1042
|
+
if (e.pointerType === 'touch') {
|
|
1043
|
+
this.hideTooltip();
|
|
1044
|
+
}
|
|
1045
|
+
},
|
|
1046
|
+
{ passive: true }
|
|
1047
|
+
);
|
|
1048
|
+
|
|
1049
|
+
// Hover (mouse/pen) and touch drag: raise country + optional floating tooltip
|
|
1050
|
+
this.mapImage.addEventListener(
|
|
1051
|
+
'pointermove',
|
|
1052
|
+
(e) => {
|
|
1053
|
+
const countryElement = getCountryFromEvent(e);
|
|
1054
|
+
if (!countryElement) {
|
|
1055
|
+
clearActive();
|
|
1056
|
+
if (this.options.showTooltips) {
|
|
1057
|
+
this.hideTooltip();
|
|
1058
|
+
}
|
|
1059
|
+
return;
|
|
1060
|
+
}
|
|
1061
|
+
|
|
1062
|
+
const mouseClickMode = e.pointerType === 'mouse' && isClickTooltip;
|
|
1063
|
+
|
|
1064
|
+
// Always raise hovered country (SVG paint order + .svgMap-active stroke)
|
|
1065
|
+
if (!this.options.showTooltips || mouseClickMode) {
|
|
1066
|
+
raiseCountry(countryElement, true);
|
|
1067
|
+
return;
|
|
1068
|
+
}
|
|
1069
|
+
|
|
1070
|
+
showCountryTooltip(countryElement, e, true);
|
|
1071
|
+
|
|
1072
|
+
if (e.pointerType === 'touch') {
|
|
1073
|
+
this.moveTooltip(e);
|
|
1074
|
+
}
|
|
1075
|
+
},
|
|
1076
|
+
{ passive: true }
|
|
1077
|
+
);
|
|
1078
|
+
|
|
956
1079
|
Object.keys(mapPaths).forEach(
|
|
957
1080
|
function (countryID) {
|
|
958
1081
|
var countryData = this.mapPaths[countryID];
|
|
@@ -970,166 +1093,22 @@ export default class svgMap {
|
|
|
970
1093
|
'id',
|
|
971
1094
|
this.id + '-map-country-' + countryID
|
|
972
1095
|
);
|
|
973
|
-
countryElement.
|
|
1096
|
+
countryElement.dataset.id = countryID;
|
|
974
1097
|
countryElement.classList.add('svgMap-country');
|
|
975
1098
|
|
|
976
1099
|
this.mapImage.appendChild(countryElement);
|
|
977
1100
|
countryElements.push(countryElement);
|
|
978
1101
|
|
|
979
|
-
// Add tooltip when touch is used
|
|
980
|
-
function handlePointerMove(e) {
|
|
981
|
-
if (e.pointerType === 'touch') return;
|
|
982
|
-
|
|
983
|
-
const target = document.elementFromPoint(e.clientX, e.clientY);
|
|
984
|
-
|
|
985
|
-
if (
|
|
986
|
-
!target ||
|
|
987
|
-
(!target.closest('.svgMap-country') &&
|
|
988
|
-
!target.closest('.svgMap-tooltip'))
|
|
989
|
-
) {
|
|
990
|
-
this.hideTooltip();
|
|
991
|
-
document
|
|
992
|
-
.querySelectorAll('.svgMap-active')
|
|
993
|
-
.forEach((el) => el.classList.remove('svgMap-active'));
|
|
994
|
-
}
|
|
995
|
-
}
|
|
996
|
-
|
|
997
|
-
const handlePointerMoveBound = handlePointerMove.bind(this);
|
|
998
|
-
|
|
999
|
-
countryElement.addEventListener(
|
|
1000
|
-
'pointerenter',
|
|
1001
|
-
function (e) {
|
|
1002
|
-
if (
|
|
1003
|
-
e.pointerType === 'mouse' &&
|
|
1004
|
-
this.options.showTooltips &&
|
|
1005
|
-
this.options.tooltipTrigger === 'click'
|
|
1006
|
-
) {
|
|
1007
|
-
return;
|
|
1008
|
-
}
|
|
1009
|
-
|
|
1010
|
-
// Only add pointermove listener for non-touch pointers
|
|
1011
|
-
if (e.pointerType !== 'touch') {
|
|
1012
|
-
document.addEventListener('pointermove', handlePointerMoveBound, {
|
|
1013
|
-
passive: true
|
|
1014
|
-
});
|
|
1015
|
-
}
|
|
1016
|
-
|
|
1017
|
-
document
|
|
1018
|
-
.querySelectorAll('.svgMap-active')
|
|
1019
|
-
.forEach((el) => el.classList.remove('svgMap-active'));
|
|
1020
|
-
|
|
1021
|
-
countryElement.parentNode.insertBefore(
|
|
1022
|
-
countryElement,
|
|
1023
|
-
this.persistentTooltipGroup || null
|
|
1024
|
-
);
|
|
1025
|
-
countryElement.classList.add('svgMap-active');
|
|
1026
|
-
|
|
1027
|
-
const countryID = countryElement.getAttribute('data-id');
|
|
1028
|
-
if (this.options.showTooltips) {
|
|
1029
|
-
this.setTooltipContent(this.getTooltipContent(countryID));
|
|
1030
|
-
this.showTooltip(e);
|
|
1031
|
-
|
|
1032
|
-
// For touch, move tooltip to the touch position and keep it there
|
|
1033
|
-
if (e.pointerType === 'touch') {
|
|
1034
|
-
this.moveTooltip(e);
|
|
1035
|
-
}
|
|
1036
|
-
}
|
|
1037
|
-
}.bind(this)
|
|
1038
|
-
);
|
|
1039
|
-
|
|
1040
|
-
// Handle touch move - update tooltip position while panning
|
|
1041
|
-
countryElement.addEventListener(
|
|
1042
|
-
'touchmove',
|
|
1043
|
-
function (e) {
|
|
1044
|
-
this.moveTooltip(e);
|
|
1045
|
-
}.bind(this),
|
|
1046
|
-
{ passive: true }
|
|
1047
|
-
);
|
|
1048
|
-
|
|
1049
|
-
// Handle touch end - remove active state and hide tooltip
|
|
1050
|
-
countryElement.addEventListener(
|
|
1051
|
-
'touchend',
|
|
1052
|
-
function (e) {
|
|
1053
|
-
const touch = e.changedTouches[0];
|
|
1054
|
-
const elementAtEnd = document.elementFromPoint(
|
|
1055
|
-
touch.clientX,
|
|
1056
|
-
touch.clientY
|
|
1057
|
-
);
|
|
1058
|
-
|
|
1059
|
-
// Only hide if touch ended outside the country or tooltip
|
|
1060
|
-
if (
|
|
1061
|
-
!elementAtEnd ||
|
|
1062
|
-
(!elementAtEnd.closest('.svgMap-country') &&
|
|
1063
|
-
!elementAtEnd.closest('.svgMap-tooltip'))
|
|
1064
|
-
) {
|
|
1065
|
-
this.hideTooltip();
|
|
1066
|
-
document
|
|
1067
|
-
.querySelectorAll('.svgMap-active')
|
|
1068
|
-
.forEach((el) => el.classList.remove('svgMap-active'));
|
|
1069
|
-
}
|
|
1070
|
-
}.bind(this),
|
|
1071
|
-
{ passive: true }
|
|
1072
|
-
);
|
|
1073
|
-
|
|
1074
|
-
// Remove pointermove listener when leaving non-touch pointer
|
|
1075
|
-
countryElement.addEventListener(
|
|
1076
|
-
'pointerleave',
|
|
1077
|
-
function (e) {
|
|
1078
|
-
if (e.pointerType !== 'touch') {
|
|
1079
|
-
document.removeEventListener(
|
|
1080
|
-
'pointermove',
|
|
1081
|
-
handlePointerMoveBound
|
|
1082
|
-
);
|
|
1083
|
-
if (
|
|
1084
|
-
!(
|
|
1085
|
-
e.pointerType === 'mouse' &&
|
|
1086
|
-
this.options.showTooltips &&
|
|
1087
|
-
this.options.tooltipTrigger === 'click'
|
|
1088
|
-
)
|
|
1089
|
-
) {
|
|
1090
|
-
this.hideTooltip();
|
|
1091
|
-
document
|
|
1092
|
-
.querySelectorAll('.svgMap-active')
|
|
1093
|
-
.forEach((el) => el.classList.remove('svgMap-active'));
|
|
1094
|
-
}
|
|
1095
|
-
}
|
|
1096
|
-
}.bind(this)
|
|
1097
|
-
);
|
|
1098
|
-
|
|
1099
|
-
document.addEventListener(
|
|
1100
|
-
'pointerover',
|
|
1101
|
-
function (e) {
|
|
1102
|
-
if (e.pointerType !== 'touch') return;
|
|
1103
|
-
|
|
1104
|
-
if (
|
|
1105
|
-
e.target.closest('.svgMap-country') ||
|
|
1106
|
-
e.target.closest('.svgMap-tooltip')
|
|
1107
|
-
) {
|
|
1108
|
-
return;
|
|
1109
|
-
}
|
|
1110
|
-
|
|
1111
|
-
this.hideTooltip();
|
|
1112
|
-
document
|
|
1113
|
-
.querySelectorAll('.svgMap-active')
|
|
1114
|
-
.forEach((el) => el.classList.remove('svgMap-active'));
|
|
1115
|
-
}.bind(this),
|
|
1116
|
-
{ passive: true }
|
|
1117
|
-
);
|
|
1118
|
-
|
|
1119
1102
|
if (
|
|
1120
1103
|
this.options.data.values &&
|
|
1121
1104
|
this.options.data.values[countryID] &&
|
|
1122
1105
|
this.options.data.values[countryID]['link']
|
|
1123
1106
|
) {
|
|
1124
|
-
countryElement.
|
|
1125
|
-
'
|
|
1126
|
-
this.options.data.values[countryID]['link']
|
|
1127
|
-
);
|
|
1107
|
+
countryElement.dataset.link =
|
|
1108
|
+
this.options.data.values[countryID]['link'];
|
|
1128
1109
|
if (this.options.data.values[countryID]['linkTarget']) {
|
|
1129
|
-
countryElement.
|
|
1130
|
-
|
|
1131
|
-
this.options.data.values[countryID]['linkTarget']
|
|
1132
|
-
);
|
|
1110
|
+
countryElement.dataset.linkTarget =
|
|
1111
|
+
this.options.data.values[countryID]['linkTarget'];
|
|
1133
1112
|
}
|
|
1134
1113
|
}
|
|
1135
1114
|
}.bind(this)
|
|
@@ -1143,6 +1122,10 @@ export default class svgMap {
|
|
|
1143
1122
|
this.createPersistentTooltips(countryElements);
|
|
1144
1123
|
}
|
|
1145
1124
|
|
|
1125
|
+
if (this.options.staticPins) {
|
|
1126
|
+
this.createStaticPins(countryElements);
|
|
1127
|
+
}
|
|
1128
|
+
|
|
1146
1129
|
let pointerStart = null;
|
|
1147
1130
|
let activeCountry = null;
|
|
1148
1131
|
|
|
@@ -1175,21 +1158,18 @@ export default class svgMap {
|
|
|
1175
1158
|
const countryElement = e.target.closest('.svgMap-country');
|
|
1176
1159
|
if (!countryElement) return;
|
|
1177
1160
|
|
|
1178
|
-
const countryID = countryElement.
|
|
1179
|
-
const link = countryElement.
|
|
1180
|
-
const linkTarget = countryElement.
|
|
1161
|
+
const countryID = countryElement.dataset.id;
|
|
1162
|
+
const link = countryElement.dataset.link;
|
|
1163
|
+
const linkTarget = countryElement.dataset.linkTarget;
|
|
1181
1164
|
const hasCallback = typeof this.options.onCountryClick === 'function';
|
|
1182
1165
|
const hasLink = !!link;
|
|
1183
1166
|
const isTouch = e.pointerType === 'touch' || e.pointerType === 'pen';
|
|
1184
1167
|
|
|
1185
|
-
const isClickTooltipMouse =
|
|
1186
|
-
e.pointerType === 'mouse' &&
|
|
1187
|
-
this.options.showTooltips &&
|
|
1188
|
-
this.options.tooltipTrigger === 'click';
|
|
1168
|
+
const isClickTooltipMouse = e.pointerType === 'mouse' && isClickTooltip;
|
|
1189
1169
|
|
|
1190
1170
|
if (!hasLink && !hasCallback && !isClickTooltipMouse) return;
|
|
1191
1171
|
|
|
1192
|
-
if (isClickTooltipMouse
|
|
1172
|
+
if (isClickTooltipMouse) {
|
|
1193
1173
|
const willNavigate =
|
|
1194
1174
|
hasLink && countryElement.classList.contains('svgMap-active');
|
|
1195
1175
|
const shouldFireCallback = hasCallback && (!hasLink || willNavigate);
|
|
@@ -1205,12 +1185,10 @@ export default class svgMap {
|
|
|
1205
1185
|
if (linkTarget) window.open(link, linkTarget);
|
|
1206
1186
|
else window.location.href = link;
|
|
1207
1187
|
} else {
|
|
1208
|
-
|
|
1209
|
-
.querySelectorAll('.svgMap-country.svgMap-active')
|
|
1210
|
-
.forEach((el) => el.classList.remove('svgMap-active'));
|
|
1188
|
+
clearActive();
|
|
1211
1189
|
countryElement.parentNode.insertBefore(
|
|
1212
1190
|
countryElement,
|
|
1213
|
-
this.persistentTooltipGroup || null
|
|
1191
|
+
this.persistentTooltipGroup || this.pinGroup || null
|
|
1214
1192
|
);
|
|
1215
1193
|
countryElement.classList.add('svgMap-active');
|
|
1216
1194
|
this.setTooltipContent(this.getTooltipContent(countryID));
|
|
@@ -1221,12 +1199,10 @@ export default class svgMap {
|
|
|
1221
1199
|
|
|
1222
1200
|
if (callbackResultClick === false) return;
|
|
1223
1201
|
|
|
1224
|
-
|
|
1225
|
-
.querySelectorAll('.svgMap-country.svgMap-active')
|
|
1226
|
-
.forEach((el) => el.classList.remove('svgMap-active'));
|
|
1202
|
+
clearActive();
|
|
1227
1203
|
countryElement.parentNode.insertBefore(
|
|
1228
1204
|
countryElement,
|
|
1229
|
-
this.persistentTooltipGroup || null
|
|
1205
|
+
this.persistentTooltipGroup || this.pinGroup || null
|
|
1230
1206
|
);
|
|
1231
1207
|
countryElement.classList.add('svgMap-active');
|
|
1232
1208
|
this.setTooltipContent(this.getTooltipContent(countryID));
|
|
@@ -1276,15 +1252,9 @@ export default class svgMap {
|
|
|
1276
1252
|
});
|
|
1277
1253
|
|
|
1278
1254
|
this._clickTooltipOutsideHandler = function (ev) {
|
|
1279
|
-
if (
|
|
1280
|
-
if (
|
|
1281
|
-
!this.options.showTooltips ||
|
|
1282
|
-
this.options.tooltipTrigger !== 'click' ||
|
|
1283
|
-
!this.tooltip
|
|
1284
|
-
) {
|
|
1255
|
+
if (!this.tooltip || !this.tooltip.classList.contains('svgMap-active')) {
|
|
1285
1256
|
return;
|
|
1286
1257
|
}
|
|
1287
|
-
if (!this.tooltip.classList.contains('svgMap-active')) return;
|
|
1288
1258
|
var node = ev.target;
|
|
1289
1259
|
if (
|
|
1290
1260
|
node &&
|
|
@@ -1302,11 +1272,6 @@ export default class svgMap {
|
|
|
1302
1272
|
});
|
|
1303
1273
|
}
|
|
1304
1274
|
}.bind(this);
|
|
1305
|
-
document.addEventListener(
|
|
1306
|
-
'pointerdown',
|
|
1307
|
-
this._clickTooltipOutsideHandler,
|
|
1308
|
-
true
|
|
1309
|
-
);
|
|
1310
1275
|
|
|
1311
1276
|
// Expose instance
|
|
1312
1277
|
var me = this;
|
|
@@ -1389,7 +1354,7 @@ export default class svgMap {
|
|
|
1389
1354
|
|
|
1390
1355
|
countryElements.forEach(
|
|
1391
1356
|
function (countryElement) {
|
|
1392
|
-
var countryID = countryElement.
|
|
1357
|
+
var countryID = countryElement.dataset.id;
|
|
1393
1358
|
if (!this.shouldShowTooltipOnLoad(countryID)) {
|
|
1394
1359
|
return;
|
|
1395
1360
|
}
|
|
@@ -1425,6 +1390,157 @@ export default class svgMap {
|
|
|
1425
1390
|
);
|
|
1426
1391
|
}
|
|
1427
1392
|
|
|
1393
|
+
// Create static pins on the map
|
|
1394
|
+
|
|
1395
|
+
createStaticPins(countryElements) {
|
|
1396
|
+
if (this.pinGroup) {
|
|
1397
|
+
this.pinGroup.remove();
|
|
1398
|
+
}
|
|
1399
|
+
|
|
1400
|
+
this.pinGroup = document.createElementNS('http://www.w3.org/2000/svg', 'g');
|
|
1401
|
+
this.pinGroup.classList.add('svgMap-pin-group');
|
|
1402
|
+
this.mapImage.appendChild(this.pinGroup);
|
|
1403
|
+
|
|
1404
|
+
countryElements.forEach(
|
|
1405
|
+
function (countryElement) {
|
|
1406
|
+
var countryID = countryElement.getAttribute('data-id');
|
|
1407
|
+
if (!this.shouldShowPin(countryID)) {
|
|
1408
|
+
return;
|
|
1409
|
+
}
|
|
1410
|
+
|
|
1411
|
+
var countryValues = this.options.data.values[countryID];
|
|
1412
|
+
var cx, cy;
|
|
1413
|
+
|
|
1414
|
+
if (
|
|
1415
|
+
countryValues &&
|
|
1416
|
+
countryValues.pinX != null &&
|
|
1417
|
+
countryValues.pinY != null
|
|
1418
|
+
) {
|
|
1419
|
+
cx = countryValues.pinX;
|
|
1420
|
+
cy = countryValues.pinY;
|
|
1421
|
+
} else {
|
|
1422
|
+
// Split the path at absolute M commands and use the largest sub-path
|
|
1423
|
+
// to avoid overseas territories (islands, colonies) skewing the center.
|
|
1424
|
+
var d = countryElement.getAttribute('d');
|
|
1425
|
+
var subPaths = d.split(/(?=M)/).filter((s) => s.trim().length > 0);
|
|
1426
|
+
var largestBB = null;
|
|
1427
|
+
var largestArea = -1;
|
|
1428
|
+
|
|
1429
|
+
subPaths.forEach(
|
|
1430
|
+
function (subPath) {
|
|
1431
|
+
var tmp = document.createElementNS(
|
|
1432
|
+
'http://www.w3.org/2000/svg',
|
|
1433
|
+
'path'
|
|
1434
|
+
);
|
|
1435
|
+
tmp.setAttribute('d', subPath);
|
|
1436
|
+
this.mapImage.appendChild(tmp);
|
|
1437
|
+
var bb = tmp.getBBox();
|
|
1438
|
+
var area = bb.width * bb.height;
|
|
1439
|
+
if (area > largestArea) {
|
|
1440
|
+
largestArea = area;
|
|
1441
|
+
largestBB = bb;
|
|
1442
|
+
}
|
|
1443
|
+
this.mapImage.removeChild(tmp);
|
|
1444
|
+
}.bind(this)
|
|
1445
|
+
);
|
|
1446
|
+
|
|
1447
|
+
cx = largestBB.x + largestBB.width / 2;
|
|
1448
|
+
cy = largestBB.y + largestBB.height / 2;
|
|
1449
|
+
}
|
|
1450
|
+
|
|
1451
|
+
var offsetX =
|
|
1452
|
+
countryValues && countryValues.pinOffsetX != null
|
|
1453
|
+
? countryValues.pinOffsetX
|
|
1454
|
+
: this.options.pinOffsetX;
|
|
1455
|
+
var offsetY =
|
|
1456
|
+
countryValues && countryValues.pinOffsetY != null
|
|
1457
|
+
? countryValues.pinOffsetY
|
|
1458
|
+
: this.options.pinOffsetY;
|
|
1459
|
+
cx += offsetX;
|
|
1460
|
+
cy += offsetY;
|
|
1461
|
+
|
|
1462
|
+
var color =
|
|
1463
|
+
(countryValues && countryValues.pinColor) || this.options.pinColor;
|
|
1464
|
+
var size =
|
|
1465
|
+
(countryValues && countryValues.pinSize) || this.options.pinSize;
|
|
1466
|
+
var strokeColor =
|
|
1467
|
+
(countryValues && countryValues.pinStrokeColor) ||
|
|
1468
|
+
this.options.pinStrokeColor;
|
|
1469
|
+
var strokeWidth =
|
|
1470
|
+
(countryValues && countryValues.pinStrokeWidth) ||
|
|
1471
|
+
this.options.pinStrokeWidth;
|
|
1472
|
+
|
|
1473
|
+
if (typeof this.options.onGetPin === 'function') {
|
|
1474
|
+
var custom = this.options.onGetPin(countryID, countryValues);
|
|
1475
|
+
if (custom) {
|
|
1476
|
+
custom.setAttribute(
|
|
1477
|
+
'transform',
|
|
1478
|
+
'translate(' + cx + ',' + cy + ')'
|
|
1479
|
+
);
|
|
1480
|
+
this.pinGroup.appendChild(custom);
|
|
1481
|
+
return;
|
|
1482
|
+
}
|
|
1483
|
+
}
|
|
1484
|
+
|
|
1485
|
+
var pinImage =
|
|
1486
|
+
(countryValues && countryValues.pinImage) || this.options.pinImage;
|
|
1487
|
+
|
|
1488
|
+
if (pinImage) {
|
|
1489
|
+
var pinW =
|
|
1490
|
+
(countryValues && countryValues.pinImageWidth) ||
|
|
1491
|
+
this.options.pinImageWidth;
|
|
1492
|
+
var pinH =
|
|
1493
|
+
(countryValues && countryValues.pinImageHeight) ||
|
|
1494
|
+
this.options.pinImageHeight;
|
|
1495
|
+
var img = document.createElementNS(
|
|
1496
|
+
'http://www.w3.org/2000/svg',
|
|
1497
|
+
'image'
|
|
1498
|
+
);
|
|
1499
|
+
img.setAttribute('href', pinImage);
|
|
1500
|
+
img.setAttribute('x', cx - pinW / 2);
|
|
1501
|
+
img.setAttribute('y', cy - pinH / 2);
|
|
1502
|
+
img.setAttribute('width', pinW);
|
|
1503
|
+
img.setAttribute('height', pinH);
|
|
1504
|
+
img.setAttribute('data-id', countryID);
|
|
1505
|
+
img.classList.add('svgMap-pin');
|
|
1506
|
+
this.pinGroup.appendChild(img);
|
|
1507
|
+
return;
|
|
1508
|
+
}
|
|
1509
|
+
|
|
1510
|
+
var circle = document.createElementNS(
|
|
1511
|
+
'http://www.w3.org/2000/svg',
|
|
1512
|
+
'circle'
|
|
1513
|
+
);
|
|
1514
|
+
circle.setAttribute('cx', cx);
|
|
1515
|
+
circle.setAttribute('cy', cy);
|
|
1516
|
+
circle.setAttribute('r', size);
|
|
1517
|
+
circle.setAttribute('fill', color);
|
|
1518
|
+
if (strokeWidth > 0) {
|
|
1519
|
+
circle.setAttribute('stroke', strokeColor);
|
|
1520
|
+
circle.setAttribute('stroke-width', strokeWidth);
|
|
1521
|
+
circle.setAttribute('vector-effect', 'non-scaling-stroke');
|
|
1522
|
+
}
|
|
1523
|
+
circle.setAttribute('data-id', countryID);
|
|
1524
|
+
circle.classList.add('svgMap-pin');
|
|
1525
|
+
this.pinGroup.appendChild(circle);
|
|
1526
|
+
}.bind(this)
|
|
1527
|
+
);
|
|
1528
|
+
}
|
|
1529
|
+
|
|
1530
|
+
// Check if a static pin should be shown for a country
|
|
1531
|
+
|
|
1532
|
+
shouldShowPin(countryID) {
|
|
1533
|
+
var pins = this.options.staticPins;
|
|
1534
|
+
var countryValues = this.options.data.values[countryID];
|
|
1535
|
+
if (Array.isArray(pins)) {
|
|
1536
|
+
return pins.indexOf(countryID) !== -1;
|
|
1537
|
+
}
|
|
1538
|
+
if (typeof pins === 'function') {
|
|
1539
|
+
return pins(countryID, countryValues);
|
|
1540
|
+
}
|
|
1541
|
+
return false;
|
|
1542
|
+
}
|
|
1543
|
+
|
|
1428
1544
|
// Check if a persistent tooltip should be shown on load
|
|
1429
1545
|
|
|
1430
1546
|
shouldShowTooltipOnLoad(countryID) {
|
|
@@ -2434,6 +2550,23 @@ export default class svgMap {
|
|
|
2434
2550
|
return;
|
|
2435
2551
|
}
|
|
2436
2552
|
this.tooltip.classList.add('svgMap-active');
|
|
2553
|
+
|
|
2554
|
+
if (
|
|
2555
|
+
this.options.showTooltips &&
|
|
2556
|
+
this.options.tooltipTrigger === 'click' &&
|
|
2557
|
+
e.pointerType === 'mouse'
|
|
2558
|
+
) {
|
|
2559
|
+
// don't register event listener in the same frame
|
|
2560
|
+
// to prevent it from being triggered immediately
|
|
2561
|
+
requestAnimationFrame(() => {
|
|
2562
|
+
document.addEventListener(
|
|
2563
|
+
'pointerdown',
|
|
2564
|
+
this._clickTooltipOutsideHandler,
|
|
2565
|
+
{ once: true, passive: true }
|
|
2566
|
+
);
|
|
2567
|
+
});
|
|
2568
|
+
}
|
|
2569
|
+
|
|
2437
2570
|
this.moveTooltip(e);
|
|
2438
2571
|
}
|
|
2439
2572
|
|
|
@@ -2443,7 +2576,12 @@ export default class svgMap {
|
|
|
2443
2576
|
if (!this.tooltip) {
|
|
2444
2577
|
return;
|
|
2445
2578
|
}
|
|
2579
|
+
|
|
2446
2580
|
this.tooltip.classList.remove('svgMap-active');
|
|
2581
|
+
document.removeEventListener(
|
|
2582
|
+
'pointerdown',
|
|
2583
|
+
this._clickTooltipOutsideHandler
|
|
2584
|
+
);
|
|
2447
2585
|
}
|
|
2448
2586
|
|
|
2449
2587
|
// Move the tooltip
|
package/src/scss/map.scss
CHANGED
|
@@ -259,3 +259,14 @@
|
|
|
259
259
|
.svgMap-wrapper.svgMap-country-click-callback .svgMap-map-wrapper .svgMap-country {
|
|
260
260
|
cursor: pointer;
|
|
261
261
|
}
|
|
262
|
+
|
|
263
|
+
// Static pins
|
|
264
|
+
|
|
265
|
+
.svgMap-pin-group {
|
|
266
|
+
pointer-events: none;
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
.svgMap-pin {
|
|
270
|
+
vector-effect: non-scaling-stroke;
|
|
271
|
+
transition: r 250ms;
|
|
272
|
+
}
|