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/README.md
CHANGED
|
@@ -102,6 +102,17 @@ You can pass the following options into svgMap:
|
|
|
102
102
|
| `showTooltips` | `boolean` | `true` | When `false`, disables hover and touch-following tooltips only. Persistent on-map labels from `persistentTooltips` are unaffected. On touch devices, countries with a `link` open it on the first tap instead of using the two-tap pattern (first tap preview, second tap navigate). |
|
|
103
103
|
| `tooltipTrigger` | `'hover'`, `'click'` | `'hover'` | How the floating tooltip opens with the **mouse**: `'hover'` opens on mouseenter/mouseleave.`'click'` opens on primary click and closes when clicking outside the map countries or tooltip. Only applies when `showTooltips` is `true`. |
|
|
104
104
|
| `persistentTooltips` | `false`, `array`, `function` | `false` | Persistent tooltips fixed on the map when it loads: an array of country IDs, or a function (`function (countryID, countryValues) { … }`) to decide per country. Independent of `showTooltips`. Best used with `showTooltips: false` or `tooltipTrigger: 'click'`. |
|
|
105
|
+
| `staticPins` | `false`, `array`, `function` | `false` | Static pins on the map at load time: an array of [ISO 3166-1 alpha-2](https://en.wikipedia.org/wiki/ISO_3166-1_alpha-2) country IDs (e.g. `['DE', 'FR']`), or a function (`function (countryID, countryValues) { … }`) to decide per country. Pins are placed at the geographic center of each country (largest landmass). Independent of `showTooltips`. |
|
|
106
|
+
| `pinColor` | `string` | `'#000000'` | Default fill color for circle pins. Accepts CSS vars, color names, rgb or hex values. Can be overridden per country via `data.values[id].pinColor`. |
|
|
107
|
+
| `pinStrokeColor` | `string` | `'#ffffff'` | Default stroke color for circle pins. Can be overridden per country via `data.values[id].pinStrokeColor`. |
|
|
108
|
+
| `pinStrokeWidth` | `number` | `1` | Default stroke width for circle pins, in screen pixels (non-scaling stroke). Can be overridden per country via `data.values[id].pinStrokeWidth`. Set to `0` for no stroke. |
|
|
109
|
+
| `pinSize` | `number` | `8` | Default radius for circle pins, in SVG units (viewBox is 2000 × 1001). Can be overridden per country via `data.values[id].pinSize`. |
|
|
110
|
+
| `pinImage` | `string` | | Image URL used as a pin instead of the default circle. Can be overridden per country via `data.values[id].pinImage`. |
|
|
111
|
+
| `pinImageWidth` | `number` | `20` | Width of the pin image in SVG units. Can be overridden per country via `data.values[id].pinImageWidth`. |
|
|
112
|
+
| `pinImageHeight` | `number` | `20` | Height of the pin image in SVG units. Can be overridden per country via `data.values[id].pinImageHeight`. |
|
|
113
|
+
| `pinOffsetX` | `number` | `0` | Horizontal offset from the pin position, in SVG units. Added after auto placement or `pinX`/`pinY`. Can be overridden per country via `data.values[id].pinOffsetX`. |
|
|
114
|
+
| `pinOffsetY` | `number` | `0` | Vertical offset from the pin position, in SVG units. Added after auto placement or `pinX`/`pinY`. Can be overridden per country via `data.values[id].pinOffsetY`. |
|
|
115
|
+
| `onGetPin` | `function` | | Custom pin element. Signature: `function (countryID, countryValues) { return svgElement; }`. Return an SVG element (e.g. `<g>`, `<path>`) to use instead of the default circle or image pin. The library positions it at the pin coordinates via `transform`. Return `null` to fall back to the default pin. |
|
|
105
116
|
| `onGetTooltip` | `function` | | Called when a tooltip is created to custimize the tooltip content (`function (tooltipDiv, countryID, countryValues) { return 'Custom HTML'; }`) |
|
|
106
117
|
| `onCountryClick` | `function` | | Called when the user clicks a country (primary button, pointer released without dragging). Signature: `function (countryID, event) { … }`. Use this for custom actions instead of or in addition to `data.values.link`. Return `false` to skip opening the URL when the country has a `link`. On touch devices with a link, the callback runs when the tap would navigate (not on the first tap that only shows the tooltip). Countries show a pointer cursor while this option is set. |
|
|
107
118
|
| `countries` | `object` | | Additional options specific to countries: |
|
|
@@ -117,6 +128,17 @@ You can pass the following options into svgMap:
|
|
|
117
128
|
| `↳ color` | `string` | | Forces a color for this country |
|
|
118
129
|
| `↳ link` | `string` | | An URL to redirect to when clicking the country |
|
|
119
130
|
| `↳ linkTarget` | `string` | | The target of the link. By default the link will be opened in the same tab. Use `'_blank'` to open the link in a new tab |
|
|
131
|
+
| `↳ pinColor` | `string` | | Pin fill color for this country (circle pins only) |
|
|
132
|
+
| `↳ pinStrokeColor` | `string` | | Pin stroke color for this country (circle pins only) |
|
|
133
|
+
| `↳ pinStrokeWidth` | `number` | | Pin stroke width for this country, in screen pixels (circle pins only) |
|
|
134
|
+
| `↳ pinSize` | `number` | | Pin radius for this country (circle pins only) |
|
|
135
|
+
| `↳ pinX` | `number` | | Pin X position in SVG units; use with `pinY` to override auto placement |
|
|
136
|
+
| `↳ pinY` | `number` | | Pin Y position in SVG units; use with `pinX` to override auto placement |
|
|
137
|
+
| `↳ pinOffsetX` | `number` | | Horizontal offset from the pin position, in SVG units (after auto placement or `pinX`/`pinY`) |
|
|
138
|
+
| `↳ pinOffsetY` | `number` | | Vertical offset from the pin position, in SVG units (after auto placement or `pinX`/`pinY`) |
|
|
139
|
+
| `↳ pinImage` | `string` | | Image URL used as the pin for this country |
|
|
140
|
+
| `↳ pinImageWidth` | `number` | | Width of the pin image for this country |
|
|
141
|
+
| `↳ pinImageHeight` | `number` | | Height of the pin image for this country |
|
|
120
142
|
| `countryNames` | `object` | | An object with the [ISO 3166-1 alpha-2](https://en.wikipedia.org/wiki/ISO_3166-1_alpha-2) country code as key and the country name as value |
|
|
121
143
|
---
|
|
122
144
|
|
package/dist/index.cjs
CHANGED
|
@@ -2375,7 +2375,34 @@ class svgMap {
|
|
|
2375
2375
|
showContinentSelector: false,
|
|
2376
2376
|
|
|
2377
2377
|
// Reset zoom on resize
|
|
2378
|
-
resetZoomOnResize: false
|
|
2378
|
+
resetZoomOnResize: false,
|
|
2379
|
+
|
|
2380
|
+
// Static pins: false | string[] | function(countryID, countryValues) => boolean
|
|
2381
|
+
staticPins: false,
|
|
2382
|
+
|
|
2383
|
+
// Default pin fill color
|
|
2384
|
+
pinColor: '#000000',
|
|
2385
|
+
|
|
2386
|
+
// Default pin stroke color and width (circle pins; width is in screen pixels with non-scaling stroke)
|
|
2387
|
+
pinStrokeColor: '#ffffff',
|
|
2388
|
+
pinStrokeWidth: 1.5,
|
|
2389
|
+
|
|
2390
|
+
// Default pin radius in SVG units (viewBox is 2000 × 1001)
|
|
2391
|
+
pinSize: 8,
|
|
2392
|
+
|
|
2393
|
+
// Custom pin element: function(countryID, countryValues) => SVGElement | null
|
|
2394
|
+
onGetPin: null,
|
|
2395
|
+
|
|
2396
|
+
// Image URL to use as a pin instead of the default circle (can also be set per-country via values[id].pinImage)
|
|
2397
|
+
pinImage: null,
|
|
2398
|
+
|
|
2399
|
+
// Width and height of the pin image in SVG units (viewBox is 2000 × 1001)
|
|
2400
|
+
pinImageWidth: 20,
|
|
2401
|
+
pinImageHeight: 20,
|
|
2402
|
+
|
|
2403
|
+
// Offset from computed pin position, in SVG units (added after auto center or pinX/pinY)
|
|
2404
|
+
pinOffsetX: 0,
|
|
2405
|
+
pinOffsetY: 0
|
|
2379
2406
|
};
|
|
2380
2407
|
|
|
2381
2408
|
this.options = Object.assign({}, defaultOptions, options);
|
|
@@ -3227,6 +3254,102 @@ class svgMap {
|
|
|
3227
3254
|
|
|
3228
3255
|
// Add map elements
|
|
3229
3256
|
var countryElements = [];
|
|
3257
|
+
|
|
3258
|
+
const clearActive = function clearActive() {
|
|
3259
|
+
this.mapImage
|
|
3260
|
+
.querySelectorAll('.svgMap-active')
|
|
3261
|
+
.forEach((el) => el.classList.remove('svgMap-active'));
|
|
3262
|
+
}.bind(this);
|
|
3263
|
+
|
|
3264
|
+
const isClickTooltip =
|
|
3265
|
+
this.options.showTooltips && this.options.tooltipTrigger === 'click';
|
|
3266
|
+
|
|
3267
|
+
const getCountryFromEvent = function (e) {
|
|
3268
|
+
return e.target && e.target.closest
|
|
3269
|
+
? e.target.closest('.svgMap-country')
|
|
3270
|
+
: null;
|
|
3271
|
+
};
|
|
3272
|
+
|
|
3273
|
+
const raiseCountry = function (countryElement, setActive) {
|
|
3274
|
+
if (setActive) {
|
|
3275
|
+
clearActive();
|
|
3276
|
+
}
|
|
3277
|
+
countryElement.parentNode.insertBefore(
|
|
3278
|
+
countryElement,
|
|
3279
|
+
this.persistentTooltipGroup || this.pinGroup || null
|
|
3280
|
+
);
|
|
3281
|
+
if (setActive) {
|
|
3282
|
+
countryElement.classList.add('svgMap-active');
|
|
3283
|
+
}
|
|
3284
|
+
}.bind(this);
|
|
3285
|
+
|
|
3286
|
+
const showCountryTooltip = function (countryElement, e, setActive) {
|
|
3287
|
+
raiseCountry(countryElement, setActive);
|
|
3288
|
+
this.setTooltipContent(this.getTooltipContent(countryElement.dataset.id));
|
|
3289
|
+
this.showTooltip(e);
|
|
3290
|
+
}.bind(this);
|
|
3291
|
+
|
|
3292
|
+
// Touch only: preview tooltip on finger down without marking country active
|
|
3293
|
+
// (active is set on pointerup so link countries keep two-tap navigation)
|
|
3294
|
+
this.mapImage.addEventListener(
|
|
3295
|
+
'pointerdown',
|
|
3296
|
+
(e) => {
|
|
3297
|
+
if (!this.options.showTooltips || e.pointerType !== 'touch') {
|
|
3298
|
+
return;
|
|
3299
|
+
}
|
|
3300
|
+
|
|
3301
|
+
const countryElement = getCountryFromEvent(e);
|
|
3302
|
+
if (!countryElement) {
|
|
3303
|
+
this.hideTooltip();
|
|
3304
|
+
return;
|
|
3305
|
+
}
|
|
3306
|
+
|
|
3307
|
+
showCountryTooltip(countryElement, e, false);
|
|
3308
|
+
this.moveTooltip(e);
|
|
3309
|
+
},
|
|
3310
|
+
{ passive: true }
|
|
3311
|
+
);
|
|
3312
|
+
|
|
3313
|
+
this.mapImage.addEventListener(
|
|
3314
|
+
'pointercancel',
|
|
3315
|
+
(e) => {
|
|
3316
|
+
if (e.pointerType === 'touch') {
|
|
3317
|
+
this.hideTooltip();
|
|
3318
|
+
}
|
|
3319
|
+
},
|
|
3320
|
+
{ passive: true }
|
|
3321
|
+
);
|
|
3322
|
+
|
|
3323
|
+
// Hover (mouse/pen) and touch drag: raise country + optional floating tooltip
|
|
3324
|
+
this.mapImage.addEventListener(
|
|
3325
|
+
'pointermove',
|
|
3326
|
+
(e) => {
|
|
3327
|
+
const countryElement = getCountryFromEvent(e);
|
|
3328
|
+
if (!countryElement) {
|
|
3329
|
+
clearActive();
|
|
3330
|
+
if (this.options.showTooltips) {
|
|
3331
|
+
this.hideTooltip();
|
|
3332
|
+
}
|
|
3333
|
+
return;
|
|
3334
|
+
}
|
|
3335
|
+
|
|
3336
|
+
const mouseClickMode = e.pointerType === 'mouse' && isClickTooltip;
|
|
3337
|
+
|
|
3338
|
+
// Always raise hovered country (SVG paint order + .svgMap-active stroke)
|
|
3339
|
+
if (!this.options.showTooltips || mouseClickMode) {
|
|
3340
|
+
raiseCountry(countryElement, true);
|
|
3341
|
+
return;
|
|
3342
|
+
}
|
|
3343
|
+
|
|
3344
|
+
showCountryTooltip(countryElement, e, true);
|
|
3345
|
+
|
|
3346
|
+
if (e.pointerType === 'touch') {
|
|
3347
|
+
this.moveTooltip(e);
|
|
3348
|
+
}
|
|
3349
|
+
},
|
|
3350
|
+
{ passive: true }
|
|
3351
|
+
);
|
|
3352
|
+
|
|
3230
3353
|
Object.keys(mapPaths).forEach(
|
|
3231
3354
|
function (countryID) {
|
|
3232
3355
|
var countryData = this.mapPaths[countryID];
|
|
@@ -3244,166 +3367,22 @@ class svgMap {
|
|
|
3244
3367
|
'id',
|
|
3245
3368
|
this.id + '-map-country-' + countryID
|
|
3246
3369
|
);
|
|
3247
|
-
countryElement.
|
|
3370
|
+
countryElement.dataset.id = countryID;
|
|
3248
3371
|
countryElement.classList.add('svgMap-country');
|
|
3249
3372
|
|
|
3250
3373
|
this.mapImage.appendChild(countryElement);
|
|
3251
3374
|
countryElements.push(countryElement);
|
|
3252
3375
|
|
|
3253
|
-
// Add tooltip when touch is used
|
|
3254
|
-
function handlePointerMove(e) {
|
|
3255
|
-
if (e.pointerType === 'touch') return;
|
|
3256
|
-
|
|
3257
|
-
const target = document.elementFromPoint(e.clientX, e.clientY);
|
|
3258
|
-
|
|
3259
|
-
if (
|
|
3260
|
-
!target ||
|
|
3261
|
-
(!target.closest('.svgMap-country') &&
|
|
3262
|
-
!target.closest('.svgMap-tooltip'))
|
|
3263
|
-
) {
|
|
3264
|
-
this.hideTooltip();
|
|
3265
|
-
document
|
|
3266
|
-
.querySelectorAll('.svgMap-active')
|
|
3267
|
-
.forEach((el) => el.classList.remove('svgMap-active'));
|
|
3268
|
-
}
|
|
3269
|
-
}
|
|
3270
|
-
|
|
3271
|
-
const handlePointerMoveBound = handlePointerMove.bind(this);
|
|
3272
|
-
|
|
3273
|
-
countryElement.addEventListener(
|
|
3274
|
-
'pointerenter',
|
|
3275
|
-
function (e) {
|
|
3276
|
-
if (
|
|
3277
|
-
e.pointerType === 'mouse' &&
|
|
3278
|
-
this.options.showTooltips &&
|
|
3279
|
-
this.options.tooltipTrigger === 'click'
|
|
3280
|
-
) {
|
|
3281
|
-
return;
|
|
3282
|
-
}
|
|
3283
|
-
|
|
3284
|
-
// Only add pointermove listener for non-touch pointers
|
|
3285
|
-
if (e.pointerType !== 'touch') {
|
|
3286
|
-
document.addEventListener('pointermove', handlePointerMoveBound, {
|
|
3287
|
-
passive: true
|
|
3288
|
-
});
|
|
3289
|
-
}
|
|
3290
|
-
|
|
3291
|
-
document
|
|
3292
|
-
.querySelectorAll('.svgMap-active')
|
|
3293
|
-
.forEach((el) => el.classList.remove('svgMap-active'));
|
|
3294
|
-
|
|
3295
|
-
countryElement.parentNode.insertBefore(
|
|
3296
|
-
countryElement,
|
|
3297
|
-
this.persistentTooltipGroup || null
|
|
3298
|
-
);
|
|
3299
|
-
countryElement.classList.add('svgMap-active');
|
|
3300
|
-
|
|
3301
|
-
const countryID = countryElement.getAttribute('data-id');
|
|
3302
|
-
if (this.options.showTooltips) {
|
|
3303
|
-
this.setTooltipContent(this.getTooltipContent(countryID));
|
|
3304
|
-
this.showTooltip(e);
|
|
3305
|
-
|
|
3306
|
-
// For touch, move tooltip to the touch position and keep it there
|
|
3307
|
-
if (e.pointerType === 'touch') {
|
|
3308
|
-
this.moveTooltip(e);
|
|
3309
|
-
}
|
|
3310
|
-
}
|
|
3311
|
-
}.bind(this)
|
|
3312
|
-
);
|
|
3313
|
-
|
|
3314
|
-
// Handle touch move - update tooltip position while panning
|
|
3315
|
-
countryElement.addEventListener(
|
|
3316
|
-
'touchmove',
|
|
3317
|
-
function (e) {
|
|
3318
|
-
this.moveTooltip(e);
|
|
3319
|
-
}.bind(this),
|
|
3320
|
-
{ passive: true }
|
|
3321
|
-
);
|
|
3322
|
-
|
|
3323
|
-
// Handle touch end - remove active state and hide tooltip
|
|
3324
|
-
countryElement.addEventListener(
|
|
3325
|
-
'touchend',
|
|
3326
|
-
function (e) {
|
|
3327
|
-
const touch = e.changedTouches[0];
|
|
3328
|
-
const elementAtEnd = document.elementFromPoint(
|
|
3329
|
-
touch.clientX,
|
|
3330
|
-
touch.clientY
|
|
3331
|
-
);
|
|
3332
|
-
|
|
3333
|
-
// Only hide if touch ended outside the country or tooltip
|
|
3334
|
-
if (
|
|
3335
|
-
!elementAtEnd ||
|
|
3336
|
-
(!elementAtEnd.closest('.svgMap-country') &&
|
|
3337
|
-
!elementAtEnd.closest('.svgMap-tooltip'))
|
|
3338
|
-
) {
|
|
3339
|
-
this.hideTooltip();
|
|
3340
|
-
document
|
|
3341
|
-
.querySelectorAll('.svgMap-active')
|
|
3342
|
-
.forEach((el) => el.classList.remove('svgMap-active'));
|
|
3343
|
-
}
|
|
3344
|
-
}.bind(this),
|
|
3345
|
-
{ passive: true }
|
|
3346
|
-
);
|
|
3347
|
-
|
|
3348
|
-
// Remove pointermove listener when leaving non-touch pointer
|
|
3349
|
-
countryElement.addEventListener(
|
|
3350
|
-
'pointerleave',
|
|
3351
|
-
function (e) {
|
|
3352
|
-
if (e.pointerType !== 'touch') {
|
|
3353
|
-
document.removeEventListener(
|
|
3354
|
-
'pointermove',
|
|
3355
|
-
handlePointerMoveBound
|
|
3356
|
-
);
|
|
3357
|
-
if (
|
|
3358
|
-
!(
|
|
3359
|
-
e.pointerType === 'mouse' &&
|
|
3360
|
-
this.options.showTooltips &&
|
|
3361
|
-
this.options.tooltipTrigger === 'click'
|
|
3362
|
-
)
|
|
3363
|
-
) {
|
|
3364
|
-
this.hideTooltip();
|
|
3365
|
-
document
|
|
3366
|
-
.querySelectorAll('.svgMap-active')
|
|
3367
|
-
.forEach((el) => el.classList.remove('svgMap-active'));
|
|
3368
|
-
}
|
|
3369
|
-
}
|
|
3370
|
-
}.bind(this)
|
|
3371
|
-
);
|
|
3372
|
-
|
|
3373
|
-
document.addEventListener(
|
|
3374
|
-
'pointerover',
|
|
3375
|
-
function (e) {
|
|
3376
|
-
if (e.pointerType !== 'touch') return;
|
|
3377
|
-
|
|
3378
|
-
if (
|
|
3379
|
-
e.target.closest('.svgMap-country') ||
|
|
3380
|
-
e.target.closest('.svgMap-tooltip')
|
|
3381
|
-
) {
|
|
3382
|
-
return;
|
|
3383
|
-
}
|
|
3384
|
-
|
|
3385
|
-
this.hideTooltip();
|
|
3386
|
-
document
|
|
3387
|
-
.querySelectorAll('.svgMap-active')
|
|
3388
|
-
.forEach((el) => el.classList.remove('svgMap-active'));
|
|
3389
|
-
}.bind(this),
|
|
3390
|
-
{ passive: true }
|
|
3391
|
-
);
|
|
3392
|
-
|
|
3393
3376
|
if (
|
|
3394
3377
|
this.options.data.values &&
|
|
3395
3378
|
this.options.data.values[countryID] &&
|
|
3396
3379
|
this.options.data.values[countryID]['link']
|
|
3397
3380
|
) {
|
|
3398
|
-
countryElement.
|
|
3399
|
-
'
|
|
3400
|
-
this.options.data.values[countryID]['link']
|
|
3401
|
-
);
|
|
3381
|
+
countryElement.dataset.link =
|
|
3382
|
+
this.options.data.values[countryID]['link'];
|
|
3402
3383
|
if (this.options.data.values[countryID]['linkTarget']) {
|
|
3403
|
-
countryElement.
|
|
3404
|
-
|
|
3405
|
-
this.options.data.values[countryID]['linkTarget']
|
|
3406
|
-
);
|
|
3384
|
+
countryElement.dataset.linkTarget =
|
|
3385
|
+
this.options.data.values[countryID]['linkTarget'];
|
|
3407
3386
|
}
|
|
3408
3387
|
}
|
|
3409
3388
|
}.bind(this)
|
|
@@ -3417,6 +3396,10 @@ class svgMap {
|
|
|
3417
3396
|
this.createPersistentTooltips(countryElements);
|
|
3418
3397
|
}
|
|
3419
3398
|
|
|
3399
|
+
if (this.options.staticPins) {
|
|
3400
|
+
this.createStaticPins(countryElements);
|
|
3401
|
+
}
|
|
3402
|
+
|
|
3420
3403
|
let pointerStart = null;
|
|
3421
3404
|
let activeCountry = null;
|
|
3422
3405
|
|
|
@@ -3449,21 +3432,18 @@ class svgMap {
|
|
|
3449
3432
|
const countryElement = e.target.closest('.svgMap-country');
|
|
3450
3433
|
if (!countryElement) return;
|
|
3451
3434
|
|
|
3452
|
-
const countryID = countryElement.
|
|
3453
|
-
const link = countryElement.
|
|
3454
|
-
const linkTarget = countryElement.
|
|
3435
|
+
const countryID = countryElement.dataset.id;
|
|
3436
|
+
const link = countryElement.dataset.link;
|
|
3437
|
+
const linkTarget = countryElement.dataset.linkTarget;
|
|
3455
3438
|
const hasCallback = typeof this.options.onCountryClick === 'function';
|
|
3456
3439
|
const hasLink = !!link;
|
|
3457
3440
|
const isTouch = e.pointerType === 'touch' || e.pointerType === 'pen';
|
|
3458
3441
|
|
|
3459
|
-
const isClickTooltipMouse =
|
|
3460
|
-
e.pointerType === 'mouse' &&
|
|
3461
|
-
this.options.showTooltips &&
|
|
3462
|
-
this.options.tooltipTrigger === 'click';
|
|
3442
|
+
const isClickTooltipMouse = e.pointerType === 'mouse' && isClickTooltip;
|
|
3463
3443
|
|
|
3464
3444
|
if (!hasLink && !hasCallback && !isClickTooltipMouse) return;
|
|
3465
3445
|
|
|
3466
|
-
if (isClickTooltipMouse
|
|
3446
|
+
if (isClickTooltipMouse) {
|
|
3467
3447
|
const willNavigate =
|
|
3468
3448
|
hasLink && countryElement.classList.contains('svgMap-active');
|
|
3469
3449
|
const shouldFireCallback = hasCallback && (!hasLink || willNavigate);
|
|
@@ -3479,12 +3459,10 @@ class svgMap {
|
|
|
3479
3459
|
if (linkTarget) window.open(link, linkTarget);
|
|
3480
3460
|
else window.location.href = link;
|
|
3481
3461
|
} else {
|
|
3482
|
-
|
|
3483
|
-
.querySelectorAll('.svgMap-country.svgMap-active')
|
|
3484
|
-
.forEach((el) => el.classList.remove('svgMap-active'));
|
|
3462
|
+
clearActive();
|
|
3485
3463
|
countryElement.parentNode.insertBefore(
|
|
3486
3464
|
countryElement,
|
|
3487
|
-
this.persistentTooltipGroup || null
|
|
3465
|
+
this.persistentTooltipGroup || this.pinGroup || null
|
|
3488
3466
|
);
|
|
3489
3467
|
countryElement.classList.add('svgMap-active');
|
|
3490
3468
|
this.setTooltipContent(this.getTooltipContent(countryID));
|
|
@@ -3495,12 +3473,10 @@ class svgMap {
|
|
|
3495
3473
|
|
|
3496
3474
|
if (callbackResultClick === false) return;
|
|
3497
3475
|
|
|
3498
|
-
|
|
3499
|
-
.querySelectorAll('.svgMap-country.svgMap-active')
|
|
3500
|
-
.forEach((el) => el.classList.remove('svgMap-active'));
|
|
3476
|
+
clearActive();
|
|
3501
3477
|
countryElement.parentNode.insertBefore(
|
|
3502
3478
|
countryElement,
|
|
3503
|
-
this.persistentTooltipGroup || null
|
|
3479
|
+
this.persistentTooltipGroup || this.pinGroup || null
|
|
3504
3480
|
);
|
|
3505
3481
|
countryElement.classList.add('svgMap-active');
|
|
3506
3482
|
this.setTooltipContent(this.getTooltipContent(countryID));
|
|
@@ -3550,15 +3526,9 @@ class svgMap {
|
|
|
3550
3526
|
});
|
|
3551
3527
|
|
|
3552
3528
|
this._clickTooltipOutsideHandler = function (ev) {
|
|
3553
|
-
if (
|
|
3554
|
-
if (
|
|
3555
|
-
!this.options.showTooltips ||
|
|
3556
|
-
this.options.tooltipTrigger !== 'click' ||
|
|
3557
|
-
!this.tooltip
|
|
3558
|
-
) {
|
|
3529
|
+
if (!this.tooltip || !this.tooltip.classList.contains('svgMap-active')) {
|
|
3559
3530
|
return;
|
|
3560
3531
|
}
|
|
3561
|
-
if (!this.tooltip.classList.contains('svgMap-active')) return;
|
|
3562
3532
|
var node = ev.target;
|
|
3563
3533
|
if (
|
|
3564
3534
|
node &&
|
|
@@ -3576,11 +3546,6 @@ class svgMap {
|
|
|
3576
3546
|
});
|
|
3577
3547
|
}
|
|
3578
3548
|
}.bind(this);
|
|
3579
|
-
document.addEventListener(
|
|
3580
|
-
'pointerdown',
|
|
3581
|
-
this._clickTooltipOutsideHandler,
|
|
3582
|
-
true
|
|
3583
|
-
);
|
|
3584
3549
|
|
|
3585
3550
|
// Expose instance
|
|
3586
3551
|
var me = this;
|
|
@@ -3663,7 +3628,7 @@ class svgMap {
|
|
|
3663
3628
|
|
|
3664
3629
|
countryElements.forEach(
|
|
3665
3630
|
function (countryElement) {
|
|
3666
|
-
var countryID = countryElement.
|
|
3631
|
+
var countryID = countryElement.dataset.id;
|
|
3667
3632
|
if (!this.shouldShowTooltipOnLoad(countryID)) {
|
|
3668
3633
|
return;
|
|
3669
3634
|
}
|
|
@@ -3699,6 +3664,157 @@ class svgMap {
|
|
|
3699
3664
|
);
|
|
3700
3665
|
}
|
|
3701
3666
|
|
|
3667
|
+
// Create static pins on the map
|
|
3668
|
+
|
|
3669
|
+
createStaticPins(countryElements) {
|
|
3670
|
+
if (this.pinGroup) {
|
|
3671
|
+
this.pinGroup.remove();
|
|
3672
|
+
}
|
|
3673
|
+
|
|
3674
|
+
this.pinGroup = document.createElementNS('http://www.w3.org/2000/svg', 'g');
|
|
3675
|
+
this.pinGroup.classList.add('svgMap-pin-group');
|
|
3676
|
+
this.mapImage.appendChild(this.pinGroup);
|
|
3677
|
+
|
|
3678
|
+
countryElements.forEach(
|
|
3679
|
+
function (countryElement) {
|
|
3680
|
+
var countryID = countryElement.getAttribute('data-id');
|
|
3681
|
+
if (!this.shouldShowPin(countryID)) {
|
|
3682
|
+
return;
|
|
3683
|
+
}
|
|
3684
|
+
|
|
3685
|
+
var countryValues = this.options.data.values[countryID];
|
|
3686
|
+
var cx, cy;
|
|
3687
|
+
|
|
3688
|
+
if (
|
|
3689
|
+
countryValues &&
|
|
3690
|
+
countryValues.pinX != null &&
|
|
3691
|
+
countryValues.pinY != null
|
|
3692
|
+
) {
|
|
3693
|
+
cx = countryValues.pinX;
|
|
3694
|
+
cy = countryValues.pinY;
|
|
3695
|
+
} else {
|
|
3696
|
+
// Split the path at absolute M commands and use the largest sub-path
|
|
3697
|
+
// to avoid overseas territories (islands, colonies) skewing the center.
|
|
3698
|
+
var d = countryElement.getAttribute('d');
|
|
3699
|
+
var subPaths = d.split(/(?=M)/).filter((s) => s.trim().length > 0);
|
|
3700
|
+
var largestBB = null;
|
|
3701
|
+
var largestArea = -1;
|
|
3702
|
+
|
|
3703
|
+
subPaths.forEach(
|
|
3704
|
+
function (subPath) {
|
|
3705
|
+
var tmp = document.createElementNS(
|
|
3706
|
+
'http://www.w3.org/2000/svg',
|
|
3707
|
+
'path'
|
|
3708
|
+
);
|
|
3709
|
+
tmp.setAttribute('d', subPath);
|
|
3710
|
+
this.mapImage.appendChild(tmp);
|
|
3711
|
+
var bb = tmp.getBBox();
|
|
3712
|
+
var area = bb.width * bb.height;
|
|
3713
|
+
if (area > largestArea) {
|
|
3714
|
+
largestArea = area;
|
|
3715
|
+
largestBB = bb;
|
|
3716
|
+
}
|
|
3717
|
+
this.mapImage.removeChild(tmp);
|
|
3718
|
+
}.bind(this)
|
|
3719
|
+
);
|
|
3720
|
+
|
|
3721
|
+
cx = largestBB.x + largestBB.width / 2;
|
|
3722
|
+
cy = largestBB.y + largestBB.height / 2;
|
|
3723
|
+
}
|
|
3724
|
+
|
|
3725
|
+
var offsetX =
|
|
3726
|
+
countryValues && countryValues.pinOffsetX != null
|
|
3727
|
+
? countryValues.pinOffsetX
|
|
3728
|
+
: this.options.pinOffsetX;
|
|
3729
|
+
var offsetY =
|
|
3730
|
+
countryValues && countryValues.pinOffsetY != null
|
|
3731
|
+
? countryValues.pinOffsetY
|
|
3732
|
+
: this.options.pinOffsetY;
|
|
3733
|
+
cx += offsetX;
|
|
3734
|
+
cy += offsetY;
|
|
3735
|
+
|
|
3736
|
+
var color =
|
|
3737
|
+
(countryValues && countryValues.pinColor) || this.options.pinColor;
|
|
3738
|
+
var size =
|
|
3739
|
+
(countryValues && countryValues.pinSize) || this.options.pinSize;
|
|
3740
|
+
var strokeColor =
|
|
3741
|
+
(countryValues && countryValues.pinStrokeColor) ||
|
|
3742
|
+
this.options.pinStrokeColor;
|
|
3743
|
+
var strokeWidth =
|
|
3744
|
+
(countryValues && countryValues.pinStrokeWidth) ||
|
|
3745
|
+
this.options.pinStrokeWidth;
|
|
3746
|
+
|
|
3747
|
+
if (typeof this.options.onGetPin === 'function') {
|
|
3748
|
+
var custom = this.options.onGetPin(countryID, countryValues);
|
|
3749
|
+
if (custom) {
|
|
3750
|
+
custom.setAttribute(
|
|
3751
|
+
'transform',
|
|
3752
|
+
'translate(' + cx + ',' + cy + ')'
|
|
3753
|
+
);
|
|
3754
|
+
this.pinGroup.appendChild(custom);
|
|
3755
|
+
return;
|
|
3756
|
+
}
|
|
3757
|
+
}
|
|
3758
|
+
|
|
3759
|
+
var pinImage =
|
|
3760
|
+
(countryValues && countryValues.pinImage) || this.options.pinImage;
|
|
3761
|
+
|
|
3762
|
+
if (pinImage) {
|
|
3763
|
+
var pinW =
|
|
3764
|
+
(countryValues && countryValues.pinImageWidth) ||
|
|
3765
|
+
this.options.pinImageWidth;
|
|
3766
|
+
var pinH =
|
|
3767
|
+
(countryValues && countryValues.pinImageHeight) ||
|
|
3768
|
+
this.options.pinImageHeight;
|
|
3769
|
+
var img = document.createElementNS(
|
|
3770
|
+
'http://www.w3.org/2000/svg',
|
|
3771
|
+
'image'
|
|
3772
|
+
);
|
|
3773
|
+
img.setAttribute('href', pinImage);
|
|
3774
|
+
img.setAttribute('x', cx - pinW / 2);
|
|
3775
|
+
img.setAttribute('y', cy - pinH / 2);
|
|
3776
|
+
img.setAttribute('width', pinW);
|
|
3777
|
+
img.setAttribute('height', pinH);
|
|
3778
|
+
img.setAttribute('data-id', countryID);
|
|
3779
|
+
img.classList.add('svgMap-pin');
|
|
3780
|
+
this.pinGroup.appendChild(img);
|
|
3781
|
+
return;
|
|
3782
|
+
}
|
|
3783
|
+
|
|
3784
|
+
var circle = document.createElementNS(
|
|
3785
|
+
'http://www.w3.org/2000/svg',
|
|
3786
|
+
'circle'
|
|
3787
|
+
);
|
|
3788
|
+
circle.setAttribute('cx', cx);
|
|
3789
|
+
circle.setAttribute('cy', cy);
|
|
3790
|
+
circle.setAttribute('r', size);
|
|
3791
|
+
circle.setAttribute('fill', color);
|
|
3792
|
+
if (strokeWidth > 0) {
|
|
3793
|
+
circle.setAttribute('stroke', strokeColor);
|
|
3794
|
+
circle.setAttribute('stroke-width', strokeWidth);
|
|
3795
|
+
circle.setAttribute('vector-effect', 'non-scaling-stroke');
|
|
3796
|
+
}
|
|
3797
|
+
circle.setAttribute('data-id', countryID);
|
|
3798
|
+
circle.classList.add('svgMap-pin');
|
|
3799
|
+
this.pinGroup.appendChild(circle);
|
|
3800
|
+
}.bind(this)
|
|
3801
|
+
);
|
|
3802
|
+
}
|
|
3803
|
+
|
|
3804
|
+
// Check if a static pin should be shown for a country
|
|
3805
|
+
|
|
3806
|
+
shouldShowPin(countryID) {
|
|
3807
|
+
var pins = this.options.staticPins;
|
|
3808
|
+
var countryValues = this.options.data.values[countryID];
|
|
3809
|
+
if (Array.isArray(pins)) {
|
|
3810
|
+
return pins.indexOf(countryID) !== -1;
|
|
3811
|
+
}
|
|
3812
|
+
if (typeof pins === 'function') {
|
|
3813
|
+
return pins(countryID, countryValues);
|
|
3814
|
+
}
|
|
3815
|
+
return false;
|
|
3816
|
+
}
|
|
3817
|
+
|
|
3702
3818
|
// Check if a persistent tooltip should be shown on load
|
|
3703
3819
|
|
|
3704
3820
|
shouldShowTooltipOnLoad(countryID) {
|
|
@@ -4708,6 +4824,23 @@ class svgMap {
|
|
|
4708
4824
|
return;
|
|
4709
4825
|
}
|
|
4710
4826
|
this.tooltip.classList.add('svgMap-active');
|
|
4827
|
+
|
|
4828
|
+
if (
|
|
4829
|
+
this.options.showTooltips &&
|
|
4830
|
+
this.options.tooltipTrigger === 'click' &&
|
|
4831
|
+
e.pointerType === 'mouse'
|
|
4832
|
+
) {
|
|
4833
|
+
// don't register event listener in the same frame
|
|
4834
|
+
// to prevent it from being triggered immediately
|
|
4835
|
+
requestAnimationFrame(() => {
|
|
4836
|
+
document.addEventListener(
|
|
4837
|
+
'pointerdown',
|
|
4838
|
+
this._clickTooltipOutsideHandler,
|
|
4839
|
+
{ once: true, passive: true }
|
|
4840
|
+
);
|
|
4841
|
+
});
|
|
4842
|
+
}
|
|
4843
|
+
|
|
4711
4844
|
this.moveTooltip(e);
|
|
4712
4845
|
}
|
|
4713
4846
|
|
|
@@ -4717,7 +4850,12 @@ class svgMap {
|
|
|
4717
4850
|
if (!this.tooltip) {
|
|
4718
4851
|
return;
|
|
4719
4852
|
}
|
|
4853
|
+
|
|
4720
4854
|
this.tooltip.classList.remove('svgMap-active');
|
|
4855
|
+
document.removeEventListener(
|
|
4856
|
+
'pointerdown',
|
|
4857
|
+
this._clickTooltipOutsideHandler
|
|
4858
|
+
);
|
|
4721
4859
|
}
|
|
4722
4860
|
|
|
4723
4861
|
// Move the tooltip
|