svgmap 2.19.2 → 2.20.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/dist/index.js CHANGED
@@ -2342,6 +2342,15 @@ class svgMap {
2342
2342
  // Set to true to open the link on mobile devices, set to false (default) to show the tooltip
2343
2343
  touchLink: false,
2344
2344
 
2345
+ // When false, disables hover/touch-following tooltips (not the on-map persistent labels; see persistentTooltips)
2346
+ showTooltips: true,
2347
+
2348
+ // 'hover' (default): mouse shows tooltip on enter. 'click': mouse opens tooltip on click; touch/pen unchanged.
2349
+ tooltipTrigger: 'hover',
2350
+
2351
+ // Persistent on-map tooltips: an array of country IDs, or a function (countryID, countryValues) => boolean
2352
+ persistentTooltips: false,
2353
+
2345
2354
  // Set to true to show the to show a zoom reset button
2346
2355
  showZoomReset: false,
2347
2356
 
@@ -2350,6 +2359,10 @@ class svgMap {
2350
2359
  return null;
2351
2360
  },
2352
2361
 
2362
+ // Called on country click (pointer released without dragging). Receives
2363
+ // (countryID, event). Return false to skip opening data.values[*].link.
2364
+ onCountryClick: null,
2365
+
2353
2366
  // Country specific options
2354
2367
  countries: {
2355
2368
  // Western Sahara: Set to false to combine Morocco (MA) and Western Sahara (EH)
@@ -2389,6 +2402,9 @@ class svgMap {
2389
2402
  // Wrapper element
2390
2403
  this.wrapper = document.getElementById(this.options.targetElementID);
2391
2404
  this.wrapper.classList.add('svgMap-wrapper');
2405
+ if (typeof this.options.onCountryClick === 'function') {
2406
+ this.wrapper.classList.add('svgMap-country-click-callback');
2407
+ }
2392
2408
 
2393
2409
  // Container element
2394
2410
  this.container = document.createElement('div');
@@ -3080,7 +3096,9 @@ class svgMap {
3080
3096
 
3081
3097
  createMap() {
3082
3098
  // Create the tooltip
3083
- this.createTooltip();
3099
+ if (this.options.showTooltips) {
3100
+ this.createTooltip();
3101
+ }
3084
3102
 
3085
3103
  // Create map wrappers
3086
3104
  this.mapWrapper = this.createElement(
@@ -3088,6 +3106,10 @@ class svgMap {
3088
3106
  'svgMap-map-wrapper',
3089
3107
  this.mapContainer
3090
3108
  );
3109
+ this.mapWrapper.style.setProperty(
3110
+ '--svg-map-country-fill',
3111
+ this.toHex(this.options.colorNoData)
3112
+ );
3091
3113
  this.mapImage = document.createElementNS(
3092
3114
  'http://www.w3.org/2000/svg',
3093
3115
  'svg'
@@ -3202,6 +3224,7 @@ class svgMap {
3202
3224
  }.bind(this);
3203
3225
 
3204
3226
  // Add map elements
3227
+ var countryElements = [];
3205
3228
  Object.keys(mapPaths).forEach(
3206
3229
  function (countryID) {
3207
3230
  var countryData = this.mapPaths[countryID];
@@ -3223,6 +3246,7 @@ class svgMap {
3223
3246
  countryElement.classList.add('svgMap-country');
3224
3247
 
3225
3248
  this.mapImage.appendChild(countryElement);
3249
+ countryElements.push(countryElement);
3226
3250
 
3227
3251
  // Add tooltip when touch is used
3228
3252
  function handlePointerMove(e) {
@@ -3238,7 +3262,7 @@ class svgMap {
3238
3262
  this.hideTooltip();
3239
3263
  document
3240
3264
  .querySelectorAll('.svgMap-active')
3241
- .forEach(el => el.classList.remove('svgMap-active'));
3265
+ .forEach((el) => el.classList.remove('svgMap-active'));
3242
3266
  }
3243
3267
  }
3244
3268
 
@@ -3247,29 +3271,40 @@ class svgMap {
3247
3271
  countryElement.addEventListener(
3248
3272
  'pointerenter',
3249
3273
  function (e) {
3274
+ if (
3275
+ e.pointerType === 'mouse' &&
3276
+ this.options.showTooltips &&
3277
+ this.options.tooltipTrigger === 'click'
3278
+ ) {
3279
+ return;
3280
+ }
3281
+
3250
3282
  // Only add pointermove listener for non-touch pointers
3251
3283
  if (e.pointerType !== 'touch') {
3252
- document.addEventListener(
3253
- 'pointermove',
3254
- handlePointerMoveBound,
3255
- { passive: true }
3256
- );
3284
+ document.addEventListener('pointermove', handlePointerMoveBound, {
3285
+ passive: true
3286
+ });
3257
3287
  }
3258
3288
 
3259
3289
  document
3260
3290
  .querySelectorAll('.svgMap-active')
3261
- .forEach(el => el.classList.remove('svgMap-active'));
3291
+ .forEach((el) => el.classList.remove('svgMap-active'));
3262
3292
 
3263
- countryElement.parentNode.appendChild(countryElement);
3293
+ countryElement.parentNode.insertBefore(
3294
+ countryElement,
3295
+ this.persistentTooltipGroup || null
3296
+ );
3264
3297
  countryElement.classList.add('svgMap-active');
3265
3298
 
3266
3299
  const countryID = countryElement.getAttribute('data-id');
3267
- this.setTooltipContent(this.getTooltipContent(countryID));
3268
- this.showTooltip(e);
3300
+ if (this.options.showTooltips) {
3301
+ this.setTooltipContent(this.getTooltipContent(countryID));
3302
+ this.showTooltip(e);
3269
3303
 
3270
- // For touch, move tooltip to the touch position and keep it there
3271
- if (e.pointerType === 'touch') {
3272
- this.moveTooltip(e);
3304
+ // For touch, move tooltip to the touch position and keep it there
3305
+ if (e.pointerType === 'touch') {
3306
+ this.moveTooltip(e);
3307
+ }
3273
3308
  }
3274
3309
  }.bind(this)
3275
3310
  );
@@ -3288,7 +3323,10 @@ class svgMap {
3288
3323
  'touchend',
3289
3324
  function (e) {
3290
3325
  const touch = e.changedTouches[0];
3291
- const elementAtEnd = document.elementFromPoint(touch.clientX, touch.clientY);
3326
+ const elementAtEnd = document.elementFromPoint(
3327
+ touch.clientX,
3328
+ touch.clientY
3329
+ );
3292
3330
 
3293
3331
  // Only hide if touch ended outside the country or tooltip
3294
3332
  if (
@@ -3299,7 +3337,7 @@ class svgMap {
3299
3337
  this.hideTooltip();
3300
3338
  document
3301
3339
  .querySelectorAll('.svgMap-active')
3302
- .forEach(el => el.classList.remove('svgMap-active'));
3340
+ .forEach((el) => el.classList.remove('svgMap-active'));
3303
3341
  }
3304
3342
  }.bind(this),
3305
3343
  { passive: true }
@@ -3310,11 +3348,22 @@ class svgMap {
3310
3348
  'pointerleave',
3311
3349
  function (e) {
3312
3350
  if (e.pointerType !== 'touch') {
3313
- document.removeEventListener('pointermove', handlePointerMoveBound);
3314
- this.hideTooltip();
3315
- document
3316
- .querySelectorAll('.svgMap-active')
3317
- .forEach(el => el.classList.remove('svgMap-active'));
3351
+ document.removeEventListener(
3352
+ 'pointermove',
3353
+ handlePointerMoveBound
3354
+ );
3355
+ if (
3356
+ !(
3357
+ e.pointerType === 'mouse' &&
3358
+ this.options.showTooltips &&
3359
+ this.options.tooltipTrigger === 'click'
3360
+ )
3361
+ ) {
3362
+ this.hideTooltip();
3363
+ document
3364
+ .querySelectorAll('.svgMap-active')
3365
+ .forEach((el) => el.classList.remove('svgMap-active'));
3366
+ }
3318
3367
  }
3319
3368
  }.bind(this)
3320
3369
  );
@@ -3334,7 +3383,7 @@ class svgMap {
3334
3383
  this.hideTooltip();
3335
3384
  document
3336
3385
  .querySelectorAll('.svgMap-active')
3337
- .forEach(el => el.classList.remove('svgMap-active'));
3386
+ .forEach((el) => el.classList.remove('svgMap-active'));
3338
3387
  }.bind(this),
3339
3388
  { passive: true }
3340
3389
  );
@@ -3358,17 +3407,29 @@ class svgMap {
3358
3407
  }.bind(this)
3359
3408
  );
3360
3409
 
3410
+ var persistent = this.options.persistentTooltips;
3411
+ if (
3412
+ persistent &&
3413
+ (Array.isArray(persistent) || typeof persistent === 'function')
3414
+ ) {
3415
+ this.createPersistentTooltips(countryElements);
3416
+ }
3417
+
3361
3418
  let pointerStart = null;
3362
3419
  let activeCountry = null;
3363
3420
 
3364
- this.mapImage.addEventListener('pointerdown', e => {
3365
- // Ignore right click (on desktop it allows inspecting the chart elements without opening the URL)
3366
- if (e.button !== 0) return;
3421
+ this.mapImage.addEventListener(
3422
+ 'pointerdown',
3423
+ (e) => {
3424
+ // Ignore right click (on desktop it allows inspecting the chart elements without opening the URL)
3425
+ if (e.button !== 0) return;
3367
3426
 
3368
- pointerStart = { x: e.clientX, y: e.clientY };
3369
- }, { passive: true });
3427
+ pointerStart = { x: e.clientX, y: e.clientY };
3428
+ },
3429
+ { passive: true }
3430
+ );
3370
3431
 
3371
- this.mapImage.addEventListener('pointerup', e => {
3432
+ this.mapImage.addEventListener('pointerup', (e) => {
3372
3433
  // Ignore right click (on desktop it allows inspecting the chart elements without opening the URL)
3373
3434
  if (e.button !== 0) return;
3374
3435
 
@@ -3388,31 +3449,137 @@ class svgMap {
3388
3449
 
3389
3450
  const countryID = countryElement.getAttribute('data-id');
3390
3451
  const link = countryElement.getAttribute('data-link');
3391
- const target = countryElement.getAttribute('data-link-target');
3392
- if (!link) return;
3393
-
3452
+ const linkTarget = countryElement.getAttribute('data-link-target');
3453
+ const hasCallback = typeof this.options.onCountryClick === 'function';
3454
+ const hasLink = !!link;
3394
3455
  const isTouch = e.pointerType === 'touch' || e.pointerType === 'pen';
3395
3456
 
3457
+ const isClickTooltipMouse =
3458
+ e.pointerType === 'mouse' &&
3459
+ this.options.showTooltips &&
3460
+ this.options.tooltipTrigger === 'click';
3461
+
3462
+ if (!hasLink && !hasCallback && !isClickTooltipMouse) return;
3463
+
3464
+ if (isClickTooltipMouse && this.options.showTooltips) {
3465
+ const willNavigate =
3466
+ hasLink && countryElement.classList.contains('svgMap-active');
3467
+ const shouldFireCallback = hasCallback && (!hasLink || willNavigate);
3468
+
3469
+ var callbackResultClick;
3470
+ if (shouldFireCallback) {
3471
+ callbackResultClick = this.options.onCountryClick(countryID, e);
3472
+ }
3473
+
3474
+ if (hasLink) {
3475
+ if (callbackResultClick === false) return;
3476
+ if (countryElement.classList.contains('svgMap-active')) {
3477
+ if (linkTarget) window.open(link, linkTarget);
3478
+ else window.location.href = link;
3479
+ } else {
3480
+ this.mapImage
3481
+ .querySelectorAll('.svgMap-country.svgMap-active')
3482
+ .forEach((el) => el.classList.remove('svgMap-active'));
3483
+ countryElement.parentNode.insertBefore(
3484
+ countryElement,
3485
+ this.persistentTooltipGroup || null
3486
+ );
3487
+ countryElement.classList.add('svgMap-active');
3488
+ this.setTooltipContent(this.getTooltipContent(countryID));
3489
+ this.showTooltip(e);
3490
+ }
3491
+ return;
3492
+ }
3493
+
3494
+ if (callbackResultClick === false) return;
3495
+
3496
+ this.mapImage
3497
+ .querySelectorAll('.svgMap-country.svgMap-active')
3498
+ .forEach((el) => el.classList.remove('svgMap-active'));
3499
+ countryElement.parentNode.insertBefore(
3500
+ countryElement,
3501
+ this.persistentTooltipGroup || null
3502
+ );
3503
+ countryElement.classList.add('svgMap-active');
3504
+ this.setTooltipContent(this.getTooltipContent(countryID));
3505
+ this.showTooltip(e);
3506
+ return;
3507
+ }
3508
+
3509
+ const willNavigate =
3510
+ hasLink &&
3511
+ (!isTouch || countryElement.classList.contains('svgMap-active'));
3512
+
3513
+ const shouldFireCallback =
3514
+ hasCallback && (!hasLink || !isTouch || willNavigate);
3515
+
3516
+ var callbackResult;
3517
+ if (shouldFireCallback) {
3518
+ callbackResult = this.options.onCountryClick(countryID, e);
3519
+ }
3520
+
3521
+ if (!hasLink) return;
3522
+
3523
+ if (callbackResult === false) return;
3524
+
3396
3525
  if (isTouch) {
3397
3526
  // Touch: only open if already active
3398
3527
  if (countryElement.classList.contains('svgMap-active')) {
3399
- if (target) window.open(link, target);
3528
+ if (linkTarget) window.open(link, linkTarget);
3400
3529
  else window.location.href = link;
3401
3530
  } else {
3402
- // first tap shows tooltip
3531
+ // first tap shows tooltip (or opens link immediately if tooltips are off)
3403
3532
  if (activeCountry) activeCountry.classList.remove('svgMap-active');
3404
3533
  activeCountry = countryElement;
3405
3534
  countryElement.classList.add('svgMap-active');
3406
- this.setTooltipContent(this.getTooltipContent(countryID));
3407
- this.showTooltip(e);
3535
+ if (this.options.showTooltips) {
3536
+ this.setTooltipContent(this.getTooltipContent(countryID));
3537
+ this.showTooltip(e);
3538
+ } else {
3539
+ if (linkTarget) window.open(link, linkTarget);
3540
+ else window.location.href = link;
3541
+ }
3408
3542
  }
3409
3543
  } else {
3410
3544
  // Desktop: open immediately
3411
- if (target) window.open(link, target);
3545
+ if (linkTarget) window.open(link, linkTarget);
3412
3546
  else window.location.href = link;
3413
3547
  }
3414
3548
  });
3415
3549
 
3550
+ this._clickTooltipOutsideHandler = function (ev) {
3551
+ if (ev.pointerType !== 'mouse') return;
3552
+ if (
3553
+ !this.options.showTooltips ||
3554
+ this.options.tooltipTrigger !== 'click' ||
3555
+ !this.tooltip
3556
+ ) {
3557
+ return;
3558
+ }
3559
+ if (!this.tooltip.classList.contains('svgMap-active')) return;
3560
+ var node = ev.target;
3561
+ if (
3562
+ node &&
3563
+ node.closest &&
3564
+ (node.closest('.svgMap-country') || node.closest('.svgMap-tooltip'))
3565
+ ) {
3566
+ return;
3567
+ }
3568
+ this.hideTooltip();
3569
+ if (this.mapImage) {
3570
+ this.mapImage
3571
+ .querySelectorAll('.svgMap-country.svgMap-active')
3572
+ .forEach(function (el) {
3573
+ el.classList.remove('svgMap-active');
3574
+ });
3575
+ }
3576
+ }.bind(this);
3577
+ document.addEventListener(
3578
+ 'pointerdown',
3579
+ this._clickTooltipOutsideHandler,
3580
+ true
3581
+ );
3582
+
3416
3583
  // Expose instance
3417
3584
  var me = this;
3418
3585
 
@@ -3478,13 +3645,82 @@ class svgMap {
3478
3645
  }
3479
3646
  }
3480
3647
 
3648
+ // Create the persistent tooltips
3649
+
3650
+ createPersistentTooltips(countryElements) {
3651
+ if (this.persistentTooltipGroup) {
3652
+ this.persistentTooltipGroup.remove();
3653
+ }
3654
+
3655
+ this.persistentTooltipGroup = document.createElementNS(
3656
+ 'http://www.w3.org/2000/svg',
3657
+ 'g'
3658
+ );
3659
+ this.persistentTooltipGroup.classList.add('svgMap-persistent-tooltips');
3660
+ this.mapImage.appendChild(this.persistentTooltipGroup);
3661
+
3662
+ countryElements.forEach(
3663
+ function (countryElement) {
3664
+ var countryID = countryElement.getAttribute('data-id');
3665
+ if (!this.shouldShowTooltipOnLoad(countryID)) {
3666
+ return;
3667
+ }
3668
+
3669
+ var boundingBox = countryElement.getBBox();
3670
+ var tooltipPosition = {
3671
+ x: boundingBox.x + boundingBox.width / 2,
3672
+ y: boundingBox.y + boundingBox.height / 2
3673
+ };
3674
+
3675
+ var tooltipObject = document.createElementNS(
3676
+ 'http://www.w3.org/2000/svg',
3677
+ 'foreignObject'
3678
+ );
3679
+ tooltipObject.setAttribute('x', tooltipPosition.x);
3680
+ tooltipObject.setAttribute('y', tooltipPosition.y);
3681
+ tooltipObject.setAttribute('width', 1);
3682
+ tooltipObject.setAttribute('height', 1);
3683
+ tooltipObject.classList.add('svgMap-persistent-tooltip-wrapper');
3684
+
3685
+ var tooltipElement = this.createElement(
3686
+ 'div',
3687
+ 'svgMap-persistent-tooltip',
3688
+ tooltipObject
3689
+ );
3690
+ tooltipElement.append(
3691
+ this.getTooltipContent(countryID, tooltipElement)
3692
+ );
3693
+ this.createElement('div', 'svgMap-tooltip-pointer', tooltipElement);
3694
+
3695
+ this.persistentTooltipGroup.appendChild(tooltipObject);
3696
+ }.bind(this)
3697
+ );
3698
+ }
3699
+
3700
+ // Check if a persistent tooltip should be shown on load
3701
+
3702
+ shouldShowTooltipOnLoad(countryID) {
3703
+ var persistent = this.options.persistentTooltips;
3704
+ var countryValues = this.options.data.values[countryID];
3705
+
3706
+ if (Array.isArray(persistent)) {
3707
+ return persistent.indexOf(countryID) !== -1;
3708
+ }
3709
+
3710
+ if (typeof persistent === 'function') {
3711
+ return persistent(countryID, countryValues);
3712
+ }
3713
+
3714
+ return false;
3715
+ }
3716
+
3481
3717
  // Create the tooltip content
3482
3718
 
3483
- getTooltipContent(countryID) {
3719
+ getTooltipContent(countryID, tooltipDiv = this.tooltip) {
3484
3720
  // Custom tooltip
3485
3721
  if (this.options.onGetTooltip) {
3486
3722
  var customDiv = this.options.onGetTooltip(
3487
- this.tooltip,
3723
+ tooltipDiv,
3488
3724
  countryID,
3489
3725
  this.options.data.values[countryID]
3490
3726
  );
@@ -4466,6 +4702,9 @@ class svgMap {
4466
4702
  // Show the tooltip
4467
4703
 
4468
4704
  showTooltip(e) {
4705
+ if (!this.tooltip) {
4706
+ return;
4707
+ }
4469
4708
  this.tooltip.classList.add('svgMap-active');
4470
4709
  this.moveTooltip(e);
4471
4710
  }
@@ -4473,12 +4712,18 @@ class svgMap {
4473
4712
  // Hide the tooltip
4474
4713
 
4475
4714
  hideTooltip() {
4715
+ if (!this.tooltip) {
4716
+ return;
4717
+ }
4476
4718
  this.tooltip.classList.remove('svgMap-active');
4477
4719
  }
4478
4720
 
4479
4721
  // Move the tooltip
4480
4722
 
4481
4723
  moveTooltip(e) {
4724
+ if (!this.tooltip) {
4725
+ return;
4726
+ }
4482
4727
  var x = e.pageX || (e.touches && e.touches[0] ? e.touches[0].pageX : null);
4483
4728
  var y = e.pageY || (e.touches && e.touches[0] ? e.touches[0].pageY : null);
4484
4729