Repository: chartjs/chartjs-plugin-zoom (js)

Issue:
Errors from zero-dimension scales
Trying to pan a zero-dimension linear scale results in a `RangeError: minimumFractionDigits value is out of range` when LinearBaseScale tries to format its ticks.

This can happen a couple of ways:

* If a scale is configured with an identical `min` and `max` - that seems like a misconfiguration, but Chart.js by itself at least attempts to handle it.
* If the browser window is shrunk enough for the chart area to have 0 dimensions

The problem occurs because `panNumericalScale` calculates a `newMin` and `newMax` of NaN [here](https://github.com/chartjs/chartjs-plugin-zoom/blob/75bce9ae07e2c64e3c98094e07a786aa4288234d/src/scale.types.js#L134). This, in turn, is because [getDecimalForPixel](https://github.com/chartjs/Chart.js/blob/v3.3.2/src/core/core.scale.js#L918) (used by getValueForPixel) and [getPixelForValue](https://github.com/chartjs/Chart.js/blob/v3.3.2/src/scales/scale.linear.js#L34) return NaN for 0-dimension scales.

I started to look at changing getDecimalForPixel and getPixelForValue to not return NaN, but I'm not sure if that's correct. (It's potentially more far-reaching, and NaN is mathematically correct.) Changing `panNumericalScale` to return true if `minLimit` or `maxLimit` are NaN seems to make sense. I can submit a PR for this if it seems correct.

Pull request:
Fix errors from zero-dimension scales
Trying to pan a zero-dimension linear scale results in a `RangeError: minimumFractionDigits value is out of range` when LinearBaseScale tries to format its ticks.

This can happen a couple of ways:

* If a scale is configured with an identical min and max - that seems like a misconfiguration, but Chart.js by itself at least attempts to handle it.
* If the browser window is shrunk enough for the chart area to have 0 dimensions

The problem occurs because panNumericalScale calculates a newMin and newMax of NaN here. This, in turn, is because getDecimalForPixel (used by getValueForPixel) and getPixelForValue return NaN for 0-dimension scales.

Fixes #543

Patch:
diff --git a/src/scale.types.js b/src/scale.types.js
index 55994e1..e8b2fcc 100644
--- a/src/scale.types.js
+++ b/src/scale.types.js
@@ -134,8 +134,11 @@ function panNumericalScale(scale, delta, limits, canZoom = false) {
   const newMin = scale.getValueForPixel(scale.getPixelForValue(prevStart + offset) - delta);
   const newMax = scale.getValueForPixel(scale.getPixelForValue(prevEnd + offset) - delta);
   const {min: minLimit = -Infinity, max: maxLimit = Infinity} = canZoom && limits && limits[scale.axis] || {};
-  if ((newMin < minLimit || newMax > maxLimit)) {
-    return true; // At limit: No change but return true to indicate no need to store the delta.
+  if (isNaN(newMin) || isNaN(newMax) || newMin < minLimit || newMax > maxLimit) {
+    // At limit: No change but return true to indicate no need to store the delta.
+    // NaN can happen for 0-dimension scales (either because they were configured
+    // with min === max or because the chart has 0 plottable area).
+    return true;
   }
   return updateRange(scale, {min: newMin, max: newMax}, limits, canZoom);
 }


Test patch:
diff --git a/test/specs/pan.spec.js b/test/specs/pan.spec.js
index d5e475c..7772575 100644
--- a/test/specs/pan.spec.js
+++ b/test/specs/pan.spec.js
@@ -92,6 +92,38 @@ describe('pan', function() {
       expect(scale.min).toBeLessThan(2);
       expect(scale.max).toBe(scale.min + 2);
     });
+
+    it('should handle zero-dimension scales', function() {
+      const chart = window.acquireChart({
+        type: 'line',
+        data,
+        options: {
+          plugins: {
+            zoom: {
+              pan: {
+                enabled: true,
+                mode: 'y',
+              }
+            }
+          },
+          scales: {
+            y: {
+              type: 'linear',
+              min: 2,
+              max: 2
+            }
+          }
+        }
+      });
+      const scale = chart.scales.y;
+      expect(scale.min).toBe(2);
+      expect(scale.max).toBe(2);
+      chart.pan(50);
+      expect(scale.min).toBe(2);
+      expect(scale.max).toBe(2);
+      expect(scale.options.min).toBe(2);
+      expect(scale.options.max).toBe(2);
+    });
   });
 
   describe('events', function() {