Skip to content

Commit 4e3a12d

Browse files
MrJohzJonathan Frerekurkle
authored
Add chart method for getting initial scale bounds (#585)
* Add chart method for getting initial scale bounds * Add types for initial scale bounds function * Always return a {min, max} object, even if the scale is not found Co-authored-by: Jukka Kurkela <[email protected]> * Add `isZoomedOrPanned` API * Add documentation for using the imperative API * Fix type and documentation tests Co-authored-by: Jonathan Frere <[email protected]> Co-authored-by: Jukka Kurkela <[email protected]>
1 parent cf1966d commit 4e3a12d

File tree

6 files changed

+174
-4
lines changed

6 files changed

+174
-4
lines changed

docs/guide/developers.md

Lines changed: 45 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,49 @@
11
# Developers
22

3+
## Imperative Zoom/Pan API
4+
5+
Alongside user-driven interactions, it is also possible to imperatively interact with the chart, either to manually zoom into a selected region, or to get information about the current zoom status.
6+
7+
### `chart.pan(delta, scales?, mode = 'none'): void`
8+
9+
Pans the current chart by the specified amount in one or more axes. The value of `delta` can be a number, in which case all axes are panned by the same amount, or it can be an `{x, y}` object to pan different amounts in the horizontal and vertical directions. The value of `scales` is a list of scale objects that should be panned - by default, all scales of the chart will be panned. The value of `mode` should be one of the Chart.js [animation modes](https://www.chartjs.org/docs/latest/configuration/animations.html#default-transitions).
10+
11+
### `chart.zoom(zoomLevel, mode = 'none'): void`
12+
13+
Zooms the current chart by the specified amount in one more axes. The value of `zoomLevel` can be a number, in which case all axes are zoomed by the same amount, or it can be an `{x, y}` object to zoom different amounts in the horizontal and vertical directions. The value of `mode` should be one of the Chart.js [animation modes](https://www.chartjs.org/docs/latest/configuration/animations.html#default-transitions).
14+
15+
### `chart.zoomScale(scaleId, newRange, mode = 'none'): void`
16+
17+
Zooms the specified scale to the range given by `newRange`. This is an object in the form `{min, max}` and represents the new bounds of that scale. The value of `mode` should be one of the Chart.js [animation modes](https://www.chartjs.org/docs/latest/configuration/animations.html#default-transitions).
18+
19+
### `chart.resetZoom(mode = 'none'): void`
20+
21+
Resets the current chart bounds to the defaults that were used before any zooming or panning occurred. The value of `mode` should be one of the Chart.js [animation modes](https://www.chartjs.org/docs/latest/configuration/animations.html#default-transitions).
22+
23+
### `chart.getZoomLevel(): number`
24+
25+
Returns the current zoom level. If this is the same as the chart's initial scales, the value returned will be `1.0`. Otherwise, the value will be less than one if the chart has been zoomed out, and more than one if it has been zoomed in. If different axes have been zoomed by different amounts, the returned value will be the zoom level of the most zoomed out axis if any have been zoomed out, otherwise it will be the zoom level of the most zoomed-in axis.
26+
27+
If the chart has been panned but not zoomed, this method will still return `1.0`.
28+
29+
### `chart.getInitialScaleBounds(): Record<string, {min: number, max: number}>`
30+
31+
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.
32+
33+
```json
34+
{
35+
x: {min: 0, max: 100},
36+
y1: {min: 50, max: 80},
37+
y2: {min: 0.1, max: 0.8}
38+
}
39+
```
40+
41+
### `chart.isZoomedOrPanned(): boolean`
42+
43+
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.
44+
45+
## Custom Scales
46+
347
You can extend chartjs-plugin-zoom with support for [custom scales](https://www.chartjs.org/docs/latest/developers/axes.html) by using the zoom plugin's `zoomFunctions` and `panFunctions` members. These objects are indexed by scale types (scales' `id` members) and give optional handlers for zoom and pan functionality.
448

549
```js
@@ -23,7 +67,7 @@ The zoom and pan functions take the following arguments:
2367
| `scale` | `Scale` | Zoom, Pan | The custom scale instance (usually derived from `Chart.Scale`)
2468
| `zoom` | `number` | Zoom | The zoom fraction; 1.0 is unzoomed, 0.5 means zoomed in to 50% of the original area, etc.
2569
| `center` | `{x, y}` | Zoom | Pixel coordinates of the center of the zoom operation. `{x: 0, y: 0}` is the upper left corner of the chart's canvas.
26-
| `pan` | `number` | Pan | Pixel amount to pan by
70+
| `delta` | `number` | Pan | Pixel amount to pan by
2771
| `limits` | [Limits](./options#limits) | Zoom, Pan | Zoom and pan limits (from chart options)
2872

2973
For examples, see chartjs-plugin-zoom's [default zoomFunctions and panFunctions handling for standard Chart.js axes](https://github.com/chartjs/chartjs-plugin-zoom/blob/v1.0.1/src/scale.types.js#L128).

src/core.js

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -202,3 +202,30 @@ export function pan(chart, delta, enabledScales, transition = 'none') {
202202
call(onPan, [{chart}]);
203203
}
204204

205+
export function getInitialScaleBounds(chart) {
206+
const state = getState(chart);
207+
const scaleBounds = {};
208+
for (const scaleId of Object.keys(chart.scales)) {
209+
const {min, max} = state.originalScaleLimits[scaleId] || {min: {}, max: {}};
210+
scaleBounds[scaleId] = {min: min.scale, max: max.scale};
211+
}
212+
213+
return scaleBounds;
214+
}
215+
216+
export function isZoomedOrPanned(chart) {
217+
const scaleBounds = getInitialScaleBounds(chart);
218+
for (const scaleId of Object.keys(chart.scales)) {
219+
const {min: originalMin, max: originalMax} = scaleBounds[scaleId];
220+
221+
if (chart.scales[scaleId].min !== originalMin) {
222+
return true;
223+
}
224+
225+
if (chart.scales[scaleId].max !== originalMax) {
226+
return true;
227+
}
228+
}
229+
230+
return false;
231+
}

src/plugin.js

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import Hammer from 'hammerjs';
22
import {addListeners, computeDragRect, removeListeners} from './handlers';
33
import {startHammer, stopHammer} from './hammer';
4-
import {pan, zoom, resetZoom, zoomScale, getZoomLevel} from './core';
4+
import {pan, zoom, resetZoom, zoomScale, getZoomLevel, getInitialScaleBounds, isZoomedOrPanned} from './core';
55
import {panFunctions, zoomFunctions} from './scale.types';
66
import {getState, removeState} from './state';
77
import {version} from '../package.json';
@@ -52,6 +52,8 @@ export default {
5252
chart.zoomScale = (id, range, transition) => zoomScale(chart, id, range, transition);
5353
chart.resetZoom = (transition) => resetZoom(chart, transition);
5454
chart.getZoomLevel = () => getZoomLevel(chart);
55+
chart.getInitialScaleBounds = () => getInitialScaleBounds(chart);
56+
chart.isZoomedOrPanned = () => isZoomedOrPanned(chart);
5557
},
5658

5759
beforeEvent(chart) {

test/specs/api.spec.js

Lines changed: 93 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,8 @@ describe('api', function() {
66
expect(typeof chart.zoom).toBe('function');
77
expect(typeof chart.zoomScale).toBe('function');
88
expect(typeof chart.resetZoom).toBe('function');
9+
expect(typeof chart.getZoomLevel).toBe('function');
10+
expect(typeof chart.getInitialScaleBounds).toBe('function');
911
});
1012

1113
describe('zoom and resetZoom', function() {
@@ -89,4 +91,95 @@ describe('api', function() {
8991
expect(chart.scales.y.max).toBe(100);
9092
});
9193
});
94+
95+
describe('getInitialScaleBounds', function() {
96+
it('should provide the correct initial scale bounds regardless of the zoom level', function() {
97+
const chart = window.acquireChart({
98+
type: 'scatter',
99+
options: {
100+
scales: {
101+
x: {
102+
min: 0,
103+
max: 100
104+
},
105+
y: {
106+
min: 0,
107+
max: 100
108+
}
109+
}
110+
}
111+
});
112+
113+
chart.zoom(1);
114+
expect(chart.getInitialScaleBounds().x.min).toBe(0);
115+
expect(chart.getInitialScaleBounds().x.max).toBe(100);
116+
expect(chart.getInitialScaleBounds().y.min).toBe(0);
117+
expect(chart.getInitialScaleBounds().y.max).toBe(100);
118+
119+
chart.zoom({x: 1.5, y: 1.25});
120+
expect(chart.getInitialScaleBounds().x.min).toBe(0);
121+
expect(chart.getInitialScaleBounds().x.max).toBe(100);
122+
expect(chart.getInitialScaleBounds().y.min).toBe(0);
123+
expect(chart.getInitialScaleBounds().y.max).toBe(100);
124+
});
125+
});
126+
127+
describe('isZoomedOrPanned', function() {
128+
it('should return whether or not the page is currently zoomed', function() {
129+
const chart = window.acquireChart({
130+
type: 'scatter',
131+
options: {
132+
scales: {
133+
x: {
134+
min: 0,
135+
max: 100
136+
},
137+
y: {
138+
min: 0,
139+
max: 100
140+
}
141+
}
142+
}
143+
});
144+
145+
chart.zoom(1);
146+
expect(chart.isZoomedOrPanned()).toBe(false);
147+
148+
chart.zoom({x: 1.5, y: 1.25});
149+
expect(chart.isZoomedOrPanned()).toBe(true);
150+
151+
chart.zoom({x: 0.25, y: 0.5});
152+
expect(chart.isZoomedOrPanned()).toBe(true);
153+
154+
chart.resetZoom();
155+
expect(chart.isZoomedOrPanned()).toBe(false);
156+
});
157+
158+
it('should return whether or not the page is currently panned', function() {
159+
const chart = window.acquireChart({
160+
type: 'scatter',
161+
options: {
162+
scales: {
163+
x: {
164+
min: 0,
165+
max: 100
166+
},
167+
y: {
168+
min: 0,
169+
max: 100
170+
}
171+
}
172+
}
173+
});
174+
175+
chart.pan({x: 0, y: 0});
176+
expect(chart.isZoomedOrPanned()).toBe(false);
177+
178+
chart.pan({x: 10});
179+
expect(chart.isZoomedOrPanned()).toBe(true);
180+
181+
chart.resetZoom();
182+
expect(chart.isZoomedOrPanned()).toBe(false);
183+
});
184+
});
92185
});

types/index.d.ts

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,10 +20,12 @@ declare module 'chart.js' {
2020
// eslint-disable-next-line @typescript-eslint/no-unused-vars
2121
interface Chart<TType extends keyof ChartTypeRegistry = keyof ChartTypeRegistry, TData = DistributiveArray<ChartTypeRegistry[TType]['defaultDataPoint']>, TLabel = unknown> {
2222
pan(pan: PanAmount, scales?: Scale[], mode?: UpdateMode): void;
23-
zoom(zoom: ZoomAmount, useTransition?: boolean, mode?: UpdateMode): void;
23+
zoom(zoom: ZoomAmount, mode?: UpdateMode): void;
2424
zoomScale(id: string, range: ScaleRange, mode?: UpdateMode): void;
2525
resetZoom(mode?: UpdateMode): void;
2626
getZoomLevel(): number;
27+
getInitialScaleBounds(): Record<string, {min: number, max: number}>;
28+
isZoomedOrPanned(): boolean;
2729
}
2830
}
2931

@@ -48,3 +50,5 @@ export function zoom(chart: Chart, amount: ZoomAmount, mode?: UpdateMode): void;
4850
export function zoomScale(chart: Chart, scaleId: string, range: ScaleRange, mode?: UpdateMode): void;
4951
export function resetZoom(chart: Chart, mode?: UpdateMode): void;
5052
export function getZoomLevel(chart: Chart): number;
53+
export function getInitialScaleBounds(chart: Chart): Record<string, {min: number, max: number}>;
54+
export function isZoomedOrPanned(chart: Chart): boolean;

types/tests/exports.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,7 @@ const chart = new Chart('id', {
5050

5151
chart.resetZoom();
5252
chart.zoom(1.1);
53-
chart.zoom({ x: 1, y: 1.1, focalPoint: { x: 10, y: 10 } }, true);
53+
chart.zoom({ x: 1, y: 1.1, focalPoint: { x: 10, y: 10 } }, 'zoom');
5454

5555
chart.pan(10);
5656
chart.pan({ x: 10, y: 20 }, [chart.scales.x]);

0 commit comments

Comments
 (0)