svgmap 2.19.3 → 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(
@@ -3206,6 +3224,7 @@ class svgMap {
3206
3224
  }.bind(this);
3207
3225
 
3208
3226
  // Add map elements
3227
+ var countryElements = [];
3209
3228
  Object.keys(mapPaths).forEach(
3210
3229
  function (countryID) {
3211
3230
  var countryData = this.mapPaths[countryID];
@@ -3227,6 +3246,7 @@ class svgMap {
3227
3246
  countryElement.classList.add('svgMap-country');
3228
3247
 
3229
3248
  this.mapImage.appendChild(countryElement);
3249
+ countryElements.push(countryElement);
3230
3250
 
3231
3251
  // Add tooltip when touch is used
3232
3252
  function handlePointerMove(e) {
@@ -3242,7 +3262,7 @@ class svgMap {
3242
3262
  this.hideTooltip();
3243
3263
  document
3244
3264
  .querySelectorAll('.svgMap-active')
3245
- .forEach(el => el.classList.remove('svgMap-active'));
3265
+ .forEach((el) => el.classList.remove('svgMap-active'));
3246
3266
  }
3247
3267
  }
3248
3268
 
@@ -3251,29 +3271,40 @@ class svgMap {
3251
3271
  countryElement.addEventListener(
3252
3272
  'pointerenter',
3253
3273
  function (e) {
3274
+ if (
3275
+ e.pointerType === 'mouse' &&
3276
+ this.options.showTooltips &&
3277
+ this.options.tooltipTrigger === 'click'
3278
+ ) {
3279
+ return;
3280
+ }
3281
+
3254
3282
  // Only add pointermove listener for non-touch pointers
3255
3283
  if (e.pointerType !== 'touch') {
3256
- document.addEventListener(
3257
- 'pointermove',
3258
- handlePointerMoveBound,
3259
- { passive: true }
3260
- );
3284
+ document.addEventListener('pointermove', handlePointerMoveBound, {
3285
+ passive: true
3286
+ });
3261
3287
  }
3262
3288
 
3263
3289
  document
3264
3290
  .querySelectorAll('.svgMap-active')
3265
- .forEach(el => el.classList.remove('svgMap-active'));
3291
+ .forEach((el) => el.classList.remove('svgMap-active'));
3266
3292
 
3267
- countryElement.parentNode.appendChild(countryElement);
3293
+ countryElement.parentNode.insertBefore(
3294
+ countryElement,
3295
+ this.persistentTooltipGroup || null
3296
+ );
3268
3297
  countryElement.classList.add('svgMap-active');
3269
3298
 
3270
3299
  const countryID = countryElement.getAttribute('data-id');
3271
- this.setTooltipContent(this.getTooltipContent(countryID));
3272
- this.showTooltip(e);
3300
+ if (this.options.showTooltips) {
3301
+ this.setTooltipContent(this.getTooltipContent(countryID));
3302
+ this.showTooltip(e);
3273
3303
 
3274
- // For touch, move tooltip to the touch position and keep it there
3275
- if (e.pointerType === 'touch') {
3276
- 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
+ }
3277
3308
  }
3278
3309
  }.bind(this)
3279
3310
  );
@@ -3292,7 +3323,10 @@ class svgMap {
3292
3323
  'touchend',
3293
3324
  function (e) {
3294
3325
  const touch = e.changedTouches[0];
3295
- const elementAtEnd = document.elementFromPoint(touch.clientX, touch.clientY);
3326
+ const elementAtEnd = document.elementFromPoint(
3327
+ touch.clientX,
3328
+ touch.clientY
3329
+ );
3296
3330
 
3297
3331
  // Only hide if touch ended outside the country or tooltip
3298
3332
  if (
@@ -3303,7 +3337,7 @@ class svgMap {
3303
3337
  this.hideTooltip();
3304
3338
  document
3305
3339
  .querySelectorAll('.svgMap-active')
3306
- .forEach(el => el.classList.remove('svgMap-active'));
3340
+ .forEach((el) => el.classList.remove('svgMap-active'));
3307
3341
  }
3308
3342
  }.bind(this),
3309
3343
  { passive: true }
@@ -3314,11 +3348,22 @@ class svgMap {
3314
3348
  'pointerleave',
3315
3349
  function (e) {
3316
3350
  if (e.pointerType !== 'touch') {
3317
- document.removeEventListener('pointermove', handlePointerMoveBound);
3318
- this.hideTooltip();
3319
- document
3320
- .querySelectorAll('.svgMap-active')
3321
- .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
+ }
3322
3367
  }
3323
3368
  }.bind(this)
3324
3369
  );
@@ -3338,7 +3383,7 @@ class svgMap {
3338
3383
  this.hideTooltip();
3339
3384
  document
3340
3385
  .querySelectorAll('.svgMap-active')
3341
- .forEach(el => el.classList.remove('svgMap-active'));
3386
+ .forEach((el) => el.classList.remove('svgMap-active'));
3342
3387
  }.bind(this),
3343
3388
  { passive: true }
3344
3389
  );
@@ -3362,17 +3407,29 @@ class svgMap {
3362
3407
  }.bind(this)
3363
3408
  );
3364
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
+
3365
3418
  let pointerStart = null;
3366
3419
  let activeCountry = null;
3367
3420
 
3368
- this.mapImage.addEventListener('pointerdown', e => {
3369
- // Ignore right click (on desktop it allows inspecting the chart elements without opening the URL)
3370
- 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;
3371
3426
 
3372
- pointerStart = { x: e.clientX, y: e.clientY };
3373
- }, { passive: true });
3427
+ pointerStart = { x: e.clientX, y: e.clientY };
3428
+ },
3429
+ { passive: true }
3430
+ );
3374
3431
 
3375
- this.mapImage.addEventListener('pointerup', e => {
3432
+ this.mapImage.addEventListener('pointerup', (e) => {
3376
3433
  // Ignore right click (on desktop it allows inspecting the chart elements without opening the URL)
3377
3434
  if (e.button !== 0) return;
3378
3435
 
@@ -3392,31 +3449,137 @@ class svgMap {
3392
3449
 
3393
3450
  const countryID = countryElement.getAttribute('data-id');
3394
3451
  const link = countryElement.getAttribute('data-link');
3395
- const target = countryElement.getAttribute('data-link-target');
3396
- if (!link) return;
3397
-
3452
+ const linkTarget = countryElement.getAttribute('data-link-target');
3453
+ const hasCallback = typeof this.options.onCountryClick === 'function';
3454
+ const hasLink = !!link;
3398
3455
  const isTouch = e.pointerType === 'touch' || e.pointerType === 'pen';
3399
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
+
3400
3525
  if (isTouch) {
3401
3526
  // Touch: only open if already active
3402
3527
  if (countryElement.classList.contains('svgMap-active')) {
3403
- if (target) window.open(link, target);
3528
+ if (linkTarget) window.open(link, linkTarget);
3404
3529
  else window.location.href = link;
3405
3530
  } else {
3406
- // first tap shows tooltip
3531
+ // first tap shows tooltip (or opens link immediately if tooltips are off)
3407
3532
  if (activeCountry) activeCountry.classList.remove('svgMap-active');
3408
3533
  activeCountry = countryElement;
3409
3534
  countryElement.classList.add('svgMap-active');
3410
- this.setTooltipContent(this.getTooltipContent(countryID));
3411
- 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
+ }
3412
3542
  }
3413
3543
  } else {
3414
3544
  // Desktop: open immediately
3415
- if (target) window.open(link, target);
3545
+ if (linkTarget) window.open(link, linkTarget);
3416
3546
  else window.location.href = link;
3417
3547
  }
3418
3548
  });
3419
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
+
3420
3583
  // Expose instance
3421
3584
  var me = this;
3422
3585
 
@@ -3482,13 +3645,82 @@ class svgMap {
3482
3645
  }
3483
3646
  }
3484
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
+
3485
3717
  // Create the tooltip content
3486
3718
 
3487
- getTooltipContent(countryID) {
3719
+ getTooltipContent(countryID, tooltipDiv = this.tooltip) {
3488
3720
  // Custom tooltip
3489
3721
  if (this.options.onGetTooltip) {
3490
3722
  var customDiv = this.options.onGetTooltip(
3491
- this.tooltip,
3723
+ tooltipDiv,
3492
3724
  countryID,
3493
3725
  this.options.data.values[countryID]
3494
3726
  );
@@ -4470,6 +4702,9 @@ class svgMap {
4470
4702
  // Show the tooltip
4471
4703
 
4472
4704
  showTooltip(e) {
4705
+ if (!this.tooltip) {
4706
+ return;
4707
+ }
4473
4708
  this.tooltip.classList.add('svgMap-active');
4474
4709
  this.moveTooltip(e);
4475
4710
  }
@@ -4477,12 +4712,18 @@ class svgMap {
4477
4712
  // Hide the tooltip
4478
4713
 
4479
4714
  hideTooltip() {
4715
+ if (!this.tooltip) {
4716
+ return;
4717
+ }
4480
4718
  this.tooltip.classList.remove('svgMap-active');
4481
4719
  }
4482
4720
 
4483
4721
  // Move the tooltip
4484
4722
 
4485
4723
  moveTooltip(e) {
4724
+ if (!this.tooltip) {
4725
+ return;
4726
+ }
4486
4727
  var x = e.pageX || (e.touches && e.touches[0] ? e.touches[0].pageX : null);
4487
4728
  var y = e.pageY || (e.touches && e.touches[0] ? e.touches[0].pageY : null);
4488
4729