Repository: chartjs/chartjs-plugin-zoom
Pull request #711 (2022-12-01 22:12:49)

Bump decode-uri-component from 0.2.0 to 0.2.2
Bumps [decode-uri-component](https://github.com/SamVerschueren/decode-uri-component) from 0.2.0 to 0.2.2.
<details>
<summary>Release notes</summary>
<p><em>Sourced from <a href="https://github.com/SamVerschueren/decode-uri-component/releases">decode-uri-component's releases</a>.</em></p>
<blockquote>
<h2>v0.2.2</h2>
<ul>
<li>Prevent overwriting previously decoded tokens  980e0bf</li>
</ul>
<p><a href="https://github.com/SamVerschueren/decode-uri-component/compare/v0.2.1...v0.2.2">https://github.com/SamVerschueren/decode-uri-component/compare/v0.2.1...v0.2.2</a></p>
<h2>v0.2.1</h2>
<ul>
<li>Switch to GitHub workflows  76abc93</li>
<li>Fix issue where decode throws - fixes <a href="https://github-redirect.dependabot.com/SamVerschueren/decode-uri-component/issues/6">#6</a>  746ca5d</li>
<li>Update license (<a href="https://github-redirect.dependabot.com/SamVerschueren/decode-uri-component/issues/1">#1</a>)  486d7e2</li>
<li>Tidelift tasks  a650457</li>
<li>Meta tweaks  66e1c28</li>
</ul>
<p><a href="https://github.com/SamVerschueren/decode-uri-component/compare/v0.2.0...v0.2.1">https://github.com/SamVerschueren/decode-uri-component/compare/v0.2.0...v0.2.1</a></p>
</blockquote>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a href="https://github.com/SamVerschueren/decode-uri-component/commit/a0eea469d26eb0df668b081672cdb9581feb78eb"><code>a0eea46</code></a> 0.2.2</li>
<li><a href="https://github.com/SamVerschueren/decode-uri-component/commit/980e0bf09b64d94f1aa79012f895816c30ffd152"><code>980e0bf</code></a> Prevent overwriting previously decoded tokens</li>
<li><a href="https://github.com/SamVerschueren/decode-uri-component/commit/3c8a373dd4837e89b3f970e01295dd03e1405a33"><code>3c8a373</code></a> 0.2.1</li>
<li><a href="https://github.com/SamVerschueren/decode-uri-component/commit/76abc939783fe3900fadb7d384a74d324d5557f3"><code>76abc93</code></a> Switch to GitHub workflows</li>
<li><a href="https://github.com/SamVerschueren/decode-uri-component/commit/746ca5dcb6667c5d364e782d53c542830e4c10b9"><code>746ca5d</code></a> Fix issue where decode throws - fixes <a href="https://github-redirect.dependabot.com/SamVerschueren/decode-uri-component/issues/6">#6</a></li>
<li><a href="https://github.com/SamVerschueren/decode-uri-component/commit/486d7e26d3a8c0fbe860fb651fe1bc98c2f2be30"><code>486d7e2</code></a> Update license (<a href="https://github-redirect.dependabot.com/SamVerschueren/decode-uri-component/issues/1">#1</a>)</li>
<li><a href="https://github.com/SamVerschueren/decode-uri-component/commit/a65045724e6234acef87f31da499d4807b20b134"><code>a650457</code></a> Tidelift tasks</li>
<li><a href="https://github.com/SamVerschueren/decode-uri-component/commit/66e1c2834c0e189201cb65196ec3101372459b02"><code>66e1c28</code></a> Meta tweaks</li>
<li>See full diff in <a href="https://github.com/SamVerschueren/decode-uri-component/compare/v0.2.0...v0.2.2">compare view</a></li>
</ul>
</details>
<br />


[![Dependabot compatibility score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=decode-uri-component&package-manager=npm_and_yarn&previous-version=0.2.0&new-version=0.2.2)](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores)

Dependabot will resolve any conflicts with this PR as long as you don't alter it yourself. You can also trigger a rebase manually by commenting `@dependabot rebase`.

[//]: # (dependabot-automerge-start)
[//]: # (dependabot-automerge-end)

---

<details>
<summary>Dependabot commands and options</summary>
<br />

You can trigger Dependabot actions by commenting on this PR:
- `@dependabot rebase` will rebase this PR
- `@dependabot recreate` will recreate this PR, overwriting any edits that have been made to it
- `@dependabot merge` will merge this PR after your CI passes on it
- `@dependabot squash and merge` will squash and merge this PR after your CI passes on it
- `@dependabot cancel merge` will cancel a previously requested merge and block automerging
- `@dependabot reopen` will reopen this PR if it is closed
- `@dependabot close` will close this PR and stop Dependabot recreating it. You can achieve the same result by closing it manually
- `@dependabot ignore this major version` will close this PR and stop Dependabot creating any more for this major version (unless you reopen the PR or upgrade to it yourself)
- `@dependabot ignore this minor version` will close this PR and stop Dependabot creating any more for this minor version (unless you reopen the PR or upgrade to it yourself)
- `@dependabot ignore this dependency` will close this PR and stop Dependabot creating any more for this dependency (unless you reopen the PR or upgrade to it yourself)
- `@dependabot use these labels` will set the current labels as the default for future PRs for this repo and language
- `@dependabot use these reviewers` will set the current reviewers as the default for future PRs for this repo and language
- `@dependabot use these assignees` will set the current assignees as the default for future PRs for this repo and language
- `@dependabot use this milestone` will set the current milestone as the default for future PRs for this repo and language

You can disable automated security fix PRs for this repo from the [Security Alerts page](https://github.com/chartjs/chartjs-plugin-zoom/network/alerts).

</details>


Patch:
diff --git a/package-lock.json b/package-lock.json
index d58acf6..c7f22a6 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -6854,9 +6854,9 @@
       }
     },
     "node_modules/decode-uri-component": {
-      "version": "0.2.0",
-      "resolved": "https://registry.npmjs.org/decode-uri-component/-/decode-uri-component-0.2.0.tgz",
-      "integrity": "sha1-6zkTMzRYd1y4TNGh+uBiEGu4dUU=",
+      "version": "0.2.2",
+      "resolved": "https://registry.npmjs.org/decode-uri-component/-/decode-uri-component-0.2.2.tgz",
+      "integrity": "sha512-FqUYQ+8o158GyGTrMFJms9qh3CqTKvAqgqsTnkLI8sKu0028orqBhxNMFkFen0zGyg6epACD32pjVk58ngIErQ==",
       "dev": true,
       "engines": {
         "node": ">=0.10"
@@ -24302,9 +24302,9 @@
       "dev": true
     },
     "decode-uri-component": {
-      "version": "0.2.0",
-      "resolved": "https://registry.npmjs.org/decode-uri-component/-/decode-uri-component-0.2.0.tgz",
-      "integrity": "sha1-6zkTMzRYd1y4TNGh+uBiEGu4dUU=",
+      "version": "0.2.2",
+      "resolved": "https://registry.npmjs.org/decode-uri-component/-/decode-uri-component-0.2.2.tgz",
+      "integrity": "sha512-FqUYQ+8o158GyGTrMFJms9qh3CqTKvAqgqsTnkLI8sKu0028orqBhxNMFkFen0zGyg6epACD32pjVk58ngIErQ==",
       "dev": true
     },
     "decompress-response": {
diff --git a/src/core.js b/src/core.js
index 2011676..77bd94c 100644
--- a/src/core.js
+++ b/src/core.js
@@ -199,6 +199,7 @@ export function pan(chart, delta, enabledScales, transition = 'none') {
 
 export function getInitialScaleBounds(chart) {
   const state = getState(chart);
+  storeOriginalScaleLimits(chart, state);
   const scaleBounds = {};
   for (const scaleId of Object.keys(chart.scales)) {
     const {min, max} = state.originalScaleLimits[scaleId] || {min: {}, max: {}};
diff --git a/src/scale.types.js b/src/scale.types.js
index 18f68cf..0d83ebf 100644
--- a/src/scale.types.js
+++ b/src/scale.types.js
@@ -47,24 +47,17 @@ export function updateRange(scale, {min, max}, limits, zoom = false) {
   const minLimit = getLimit(state, scale, scaleLimits, 'min', -Infinity);
   const maxLimit = getLimit(state, scale, scaleLimits, 'max', Infinity);
 
-  const cmin = Math.max(min, minLimit);
-  const cmax = Math.min(max, maxLimit);
-  const range = zoom ? Math.max(cmax - cmin, minRange) : scale.max - scale.min;
-  if (cmax - cmin !== range) {
-    if (minLimit > cmax - range) {
-      min = cmin;
-      max = cmin + range;
-    } else if (maxLimit < cmin + range) {
-      max = cmax;
-      min = cmax - range;
-    } else {
-      const offset = (range - cmax + cmin) / 2;
-      min = cmin - offset;
-      max = cmax + offset;
-    }
-  } else {
-    min = cmin;
-    max = cmax;
+  const range = zoom ? Math.max(max - min, minRange) : scale.max - scale.min;
+  const offset = (range - max + min) / 2;
+  min -= offset;
+  max += offset;
+
+  if (min < minLimit) {
+    min = minLimit;
+    max = Math.min(minLimit + range, maxLimit);
+  } else if (max > maxLimit) {
+    max = maxLimit;
+    min = Math.max(maxLimit - range, minLimit);
   }
   scaleOpts.min = min;
   scaleOpts.max = max;


Test patch:
diff --git a/test/specs/api.spec.js b/test/specs/api.spec.js
index b11ef5e..181bf47 100644
--- a/test/specs/api.spec.js
+++ b/test/specs/api.spec.js
@@ -91,6 +91,107 @@ describe('api', function() {
       expect(chart.scales.y.min).toBe(0);
       expect(chart.scales.y.max).toBe(100);
     });
+
+    it('should honor fitted scale updates on reset', function() {
+      const chart = window.acquireChart({
+        type: 'scatter',
+        data: {
+          datasets: [
+            {
+              data: [
+                {x: 0, y: 0},
+                {x: 100, y: 100}
+              ]
+            }
+          ]
+        }
+      });
+
+      chart.zoom(1.5);
+      chart.data.datasets[0].data[0].x = -100;
+      chart.update();
+      chart.resetZoom();
+      expect(chart.scales.x.min).toBe(-100);
+      expect(chart.scales.x.max).toBe(100);
+    });
+
+    it('should no-op with fully constrained limits', function() {
+      const chart = window.acquireChart({
+        type: 'scatter',
+        options: {
+          scales: {
+            x: {
+              min: 0,
+              max: 100
+            },
+            y: {
+              min: 0,
+              max: 100
+            }
+          },
+          plugins: {
+            zoom: {
+              limits: {
+                x: {
+                  min: 0,
+                  max: 100,
+                  minRange: 100
+                }
+              }
+            }
+          }
+        }
+      });
+
+      chart.zoom(1.5);
+      expect(chart.scales.x.min).toBe(0);
+      expect(chart.scales.x.max).toBe(100);
+    });
+
+    it('should honor zoom changes against a limit', function() {
+      const chart = window.acquireChart({
+        type: 'scatter',
+        options: {
+          scales: {
+            x: {
+              min: 0,
+              max: 100
+            },
+            y: {
+              min: 0,
+              max: 100
+            }
+          },
+          plugins: {
+            zoom: {
+              limits: {
+                x: {
+                  min: 0,
+                  max: 100
+                }
+              }
+            }
+          }
+        }
+      });
+      chart.zoom({
+        x: 1.99,
+        focalPoint: {
+          x: chart.scales.x.getPixelForValue(0)
+        }
+      });
+      expect(chart.scales.x.min).toBe(0);
+      expect(chart.scales.x.max).toBe(1);
+
+      chart.zoom({
+        x: -98,
+        focalPoint: {
+          x: chart.scales.x.getPixelForValue(1)
+        }
+      });
+      expect(chart.scales.x.min).toBe(0);
+      expect(chart.scales.x.max).toBe(100);
+    });
   });
 
   describe('getInitialScaleBounds', function() {
@@ -123,6 +224,35 @@ describe('api', function() {
       expect(chart.getInitialScaleBounds().y.min).toBe(0);
       expect(chart.getInitialScaleBounds().y.max).toBe(100);
     });
+
+    it('should provide updated scale bounds upon data update', function() {
+      const chart = window.acquireChart({
+        type: 'scatter',
+        data: {
+          datasets: [
+            {
+              data: [
+                {x: 0, y: 0},
+                {x: 100, y: 100}
+              ]
+            }
+          ]
+        }
+      });
+
+      expect(chart.getInitialScaleBounds().x.min).toBe(0);
+      expect(chart.getInitialScaleBounds().x.max).toBe(100);
+      expect(chart.getInitialScaleBounds().y.min).toBe(0);
+      expect(chart.getInitialScaleBounds().y.max).toBe(100);
+
+      chart.data.datasets[0].data[0].x = -100;
+      chart.update();
+
+      expect(chart.getInitialScaleBounds().x.min).toBe(-100);
+      expect(chart.getInitialScaleBounds().x.max).toBe(100);
+      expect(chart.getInitialScaleBounds().y.min).toBe(0);
+      expect(chart.getInitialScaleBounds().y.max).toBe(100);
+    });
   });
 
   describe('isZoomedOrPanned', function() {
@@ -186,5 +316,30 @@ describe('api', function() {
       chart.resetZoom();
       expect(chart.isZoomedOrPanned()).toBe(false);
     });
+
+    it('should work with updated data and fitted scales', function() {
+      const chart = window.acquireChart({
+        type: 'scatter',
+        data: {
+          datasets: [
+            {
+              data: [
+                {x: 0, y: 0},
+                {x: 100, y: 100}
+              ]
+            }
+          ]
+        }
+      });
+
+      // This sequence of operations captures a previous bug.
+      chart.zoom(1.5);
+      chart.data.datasets[0].data[0].x = -100;
+      chart.update();
+      chart.resetZoom();
+      expect(chart.scales.x.min).toBe(-100);
+      expect(chart.scales.x.max).toBe(100);
+      expect(chart.isZoomedOrPanned()).toBe(false);
+    });
   });
 });
diff --git a/test/specs/zoom.spec.js b/test/specs/zoom.spec.js
index bb76e5c..add2eb7 100644
--- a/test/specs/zoom.spec.js
+++ b/test/specs/zoom.spec.js
@@ -933,7 +933,7 @@ describe('zoom', function() {
       expect(chart.scales.y.min).toBe(3);
       expect(chart.scales.y.max).toBe(5);
       chart.zoom(0.9);
-      expect(chart.scales.y.min).toBe(2);
+      expect(chart.scales.y.min).toBe(1);
       expect(chart.scales.y.max).toBe(5);
       chart.zoom(0.9);
       expect(chart.scales.y.min).toBe(1);