Repository: chartjs/chartjs-plugin-zoom
Pull request #780 (2023-09-04 21:48:44)

Add a function for getting zoom range
This adds a function, `getZoomedScaleBounds`, to act as a counterpart to `getInitialScaleBounds` (getting the scale ranges after zoom rather than before) and `zoomScale` (reading, rather than writing, each scale's zoom).

A question for code review: Is it okay to return the internal object directly, or should I return a new object to ensure that it can't be mutated?

trullock: This is merged into my fork at https://github.com/trullock/chartjs-plugin-zoom which is published on npm as @trullock/chartjs-plugin-zoom, as this fork seems abandoned
kurkle: @joshkel would you be able to resolve the merge conflicts in this?

Patch:
diff --git a/docs/guide/developers.md b/docs/guide/developers.md
index 63f5759..d1ea5cf 100644
--- a/docs/guide/developers.md
+++ b/docs/guide/developers.md
@@ -26,7 +26,7 @@ Returns the current zoom level.  If this is the same as the chart's initial scal
 
 If the chart has been panned but not zoomed, this method will still return `1.0`.
 
-### `chart.getInitialScaleBounds(): Record<string, {min: number, max: number}>`
+### `chart.getInitialScaleBounds(): Record<string, {min: number | undefined, max: number | undefined}>`
 
 Returns the initial scale bounds of each scale before any zooming or panning took place.  This is returned in the format of an object, e.g.
 
@@ -38,6 +38,20 @@ Returns the initial scale bounds of each scale before any zooming or panning too
 }
 ```
 
+### `chart.getZoomedScaleBounds(): Record<string, {min: number, max: number}>`
+
+Returns the updated scale bounds of each scale after any zooming or panning took place.  This is returned in the format of an object, e.g.
+
+```json
+{
+  x: {min: 25, max: 75},
+  y1: {min: 60, max: 90},
+  y2: undefined
+}
+```
+
+Scale IDs that have not been zoomed will be `undefined` within the returned object.
+
 ### `chart.isZoomedOrPanned(): boolean`
 
 Returns whether the chart has been zoomed or panned - i.e. whether the initial scale of any axis is different to the one used currently.
diff --git a/src/core.js b/src/core.js
index 336fce6..0b93b15 100644
--- a/src/core.js
+++ b/src/core.js
@@ -127,6 +127,7 @@ export function resetZoom(chart, transition = 'default') {
       delete scaleOptions.min;
       delete scaleOptions.max;
     }
+    delete state.updatedScaleLimits[scale.id];
   });
   chart.update(transition);
   call(state.options.zoom.onZoomComplete, [{chart}]);
@@ -209,6 +210,16 @@ export function getInitialScaleBounds(chart) {
   return scaleBounds;
 }
 
+export function getZoomedScaleBounds(chart) {
+  const state = getState(chart);
+  const scaleBounds = {};
+  for (const scaleId of Object.keys(chart.scales)) {
+    scaleBounds[scaleId] = state.updatedScaleLimits[scaleId];
+  }
+
+  return scaleBounds;
+}
+
 export function isZoomedOrPanned(chart) {
   const scaleBounds = getInitialScaleBounds(chart);
   for (const scaleId of Object.keys(chart.scales)) {
diff --git a/src/handlers.js b/src/handlers.js
index f9c195c..9c11851 100644
--- a/src/handlers.js
+++ b/src/handlers.js
@@ -1,6 +1,6 @@
 import {directionEnabled, debounce, keyNotPressed, getModifierKey, keyPressed} from './utils';
 import {zoom, zoomRect} from './core';
-import {callback as call, getRelativePosition} from 'chart.js/helpers';
+import {callback as call, getRelativePosition, _isPointInArea} from 'chart.js/helpers';
 import {getState} from './state';
 
 function removeHandler(chart, type) {
@@ -61,6 +61,12 @@ function zoomStart(chart, event, zoomOptions) {
 }
 
 export function mouseDown(chart, event) {
+  if (chart.legend) {
+    const point = getRelativePosition(event, chart);
+    if (_isPointInArea(point, chart.legend)) {
+      return;
+    }
+  }
   const state = getState(chart);
   const {pan: panOptions, zoom: zoomOptions = {}} = state.options;
   if (
diff --git a/src/plugin.js b/src/plugin.js
index bd98b34..acdd23e 100644
--- a/src/plugin.js
+++ b/src/plugin.js
@@ -1,7 +1,7 @@
 import Hammer from 'hammerjs';
 import {addListeners, computeDragRect, removeListeners} from './handlers';
 import {startHammer, stopHammer} from './hammer';
-import {pan, zoom, resetZoom, zoomScale, getZoomLevel, getInitialScaleBounds, isZoomedOrPanned, isZoomingOrPanning, zoomRect} from './core';
+import {pan, zoom, resetZoom, zoomScale, getZoomLevel, getInitialScaleBounds, getZoomedScaleBounds, isZoomedOrPanned, isZoomingOrPanning, zoomRect} from './core';
 import {panFunctions, zoomFunctions, zoomRectFunctions} from './scale.types';
 import {getState, removeState} from './state';
 import {version} from '../package.json';
@@ -82,6 +82,7 @@ export default {
     chart.resetZoom = (transition) => resetZoom(chart, transition);
     chart.getZoomLevel = () => getZoomLevel(chart);
     chart.getInitialScaleBounds = () => getInitialScaleBounds(chart);
+    chart.getZoomedScaleBounds = () => getZoomedScaleBounds(chart);
     chart.isZoomedOrPanned = () => isZoomedOrPanned(chart);
     chart.isZoomingOrPanning = () => isZoomingOrPanning(chart);
   },
diff --git a/types/index.d.ts b/types/index.d.ts
index 864ec45..db6e961 100644
--- a/types/index.d.ts
+++ b/types/index.d.ts
@@ -25,7 +25,8 @@ declare module 'chart.js' {
     zoomScale(id: string, range: ScaleRange, mode?: UpdateMode): void;
     resetZoom(mode?: UpdateMode): void;
     getZoomLevel(): number;
-    getInitialScaleBounds(): Record<string, {min: number, max: number}>;
+    getInitialScaleBounds(): Record<string, {min: number | undefined, max: number | undefined}>;
+    getZoomedScaleBounds(): Record<string, ScaleRange | undefined>;
     isZoomedOrPanned(): boolean;
     isZoomingOrPanning(): boolean;
   }
@@ -55,6 +56,7 @@ export function zoomRect(chart: Chart, p0: Point, p1: Point, mode?: UpdateMode):
 export function zoomScale(chart: Chart, scaleId: string, range: ScaleRange, mode?: UpdateMode): void;
 export function resetZoom(chart: Chart, mode?: UpdateMode): void;
 export function getZoomLevel(chart: Chart): number;
-export function getInitialScaleBounds(chart: Chart): Record<string, {min: number, max: number}>;
+export function getInitialScaleBounds(chart: Chart): Record<string, {min: number | undefined, max: number | undefined}>;
+export function getZoomedScaleBounds(chart: Chart): Record<string, ScaleRange | undefined>;
 export function isZoomedOrPanned(chart: Chart): boolean;
 export function isZoomingOrPanning(chart: Chart): boolean;


Test patch:
diff --git a/test/specs/api.spec.js b/test/specs/api.spec.js
index 181bf47..5f21ef7 100644
--- a/test/specs/api.spec.js
+++ b/test/specs/api.spec.js
@@ -342,4 +342,31 @@ describe('api', function() {
       expect(chart.isZoomedOrPanned()).toBe(false);
     });
   });
+
+  describe('getZoomedScaleBounds', function() {
+    it('should return the zoom range, or undefined if not zoomed', function() {
+      const chart = window.acquireChart({
+        type: 'scatter',
+        options: {
+          scales: {
+            x: {
+              min: 0,
+              max: 100
+            },
+            y: {
+              min: 0,
+              max: 100
+            }
+          }
+        }
+      });
+      expect(chart.getZoomedScaleBounds().x).toBeUndefined();
+
+      chart.zoom(1.5);
+      expect(chart.getZoomedScaleBounds().x).toEqual({min: 25, max: 75});
+
+      chart.resetZoom();
+      expect(chart.getZoomedScaleBounds().x).toBeUndefined();
+    });
+  });
 });