Skip to content

Commit edcf386

Browse files
authored
feat: add trigger to onZoom context (#901)
* feat: add trigger to onZoom context * fix: try to fix lint * fix: lint
1 parent 5322427 commit edcf386

9 files changed

+114
-56
lines changed

src/core.js

+56-9
Original file line numberDiff line numberDiff line change
@@ -57,11 +57,12 @@ function getCenter(chart) {
5757
}
5858

5959
/**
60-
* @param chart The chart instance
61-
* @param {number | {x?: number, y?: number, focalPoint?: {x: number, y: number}}} amount The zoom percentage or percentages and focal point
62-
* @param {string} [transition] Which transition mode to use. Defaults to 'none'
60+
* @param {import('chart.js').Chart} chart The Chart instance
61+
* @param {import('../types').ZoomAmount} amount The zoom percentage or percentages and focal point
62+
* @param {import('chart.js').UpdateMode} [transition] Which transition mode to use. Defaults to 'none'
63+
* @param {import('../types/options').ZoomTrigger} [trigger] What triggered the zoom. Defaults to 'api'
6364
*/
64-
export function zoom(chart, amount, transition = 'none') {
65+
export function zoom(chart, amount, transition = 'none', trigger = 'api') {
6566
const {x = 1, y = 1, focalPoint = getCenter(chart)} = typeof amount === 'number' ? {x: amount, y: amount} : amount;
6667
const state = getState(chart);
6768
const {options: {limits, zoom: zoomOptions}} = state;
@@ -72,6 +73,7 @@ export function zoom(chart, amount, transition = 'none') {
7273
const yEnabled = y !== 1;
7374
const enabledScales = getEnabledScalesByPoint(zoomOptions, focalPoint, chart);
7475

76+
// @ts-expect-error No overload matches this call
7577
each(enabledScales || chart.scales, function(scale) {
7678
if (scale.isHorizontal() && xEnabled) {
7779
doZoom(scale, x, focalPoint, limits);
@@ -82,10 +84,18 @@ export function zoom(chart, amount, transition = 'none') {
8284

8385
chart.update(transition);
8486

85-
call(zoomOptions.onZoom, [{chart}]);
87+
// @ts-expect-error args not assignable to unknown[]
88+
call(zoomOptions.onZoom, [{chart, trigger}]);
8689
}
8790

88-
export function zoomRect(chart, p0, p1, transition = 'none') {
91+
/**
92+
* @param {import('chart.js').Chart} chart The Chart instance
93+
* @param {import('chart.js').Point} p0 First corner of the rect
94+
* @param {import('chart.js').Point} p1 Opposite corner of the rect
95+
* @param {import('chart.js').UpdateMode} [transition]
96+
* @param {import('../types/options').ZoomTrigger} [trigger] What triggered the zoom. Defaults to 'api'
97+
*/
98+
export function zoomRect(chart, p0, p1, transition = 'none', trigger = 'api') {
8999
const state = getState(chart);
90100
const {options: {limits, zoom: zoomOptions}} = state;
91101
const {mode = 'xy'} = zoomOptions;
@@ -104,19 +114,32 @@ export function zoomRect(chart, p0, p1, transition = 'none') {
104114

105115
chart.update(transition);
106116

107-
call(zoomOptions.onZoom, [{chart}]);
117+
// @ts-expect-error args not assignable to unknown[]
118+
call(zoomOptions.onZoom, [{chart, trigger}]);
108119
}
109120

110-
export function zoomScale(chart, scaleId, range, transition = 'none') {
121+
/**
122+
* @param {import('chart.js').Chart} chart The Chart instance
123+
* @param {string} scaleId
124+
* @param {import('../types').ScaleRange} range
125+
* @param {import('chart.js').UpdateMode} [transition]
126+
* @param {import('../types/options').ZoomTrigger} [trigger] What triggered the zoom. Defaults to 'api'
127+
*/
128+
export function zoomScale(chart, scaleId, range, transition = 'none', trigger = 'api') {
111129
const state = getState(chart);
112130
storeOriginalScaleLimits(chart, state);
113131
const scale = chart.scales[scaleId];
114132
updateRange(scale, range, undefined, true);
115133
chart.update(transition);
116134

117-
call(state.options.zoom?.onZoom, [{chart}]);
135+
// @ts-expect-error args not assignable to unknown[]
136+
call(state.options.zoom?.onZoom, [{chart, trigger}]);
118137
}
119138

139+
/**
140+
* @param {import('chart.js').Chart} chart The Chart instance
141+
* @param {import('chart.js').UpdateMode} transition
142+
*/
120143
export function resetZoom(chart, transition = 'default') {
121144
const state = getState(chart);
122145
const originalScaleLimits = storeOriginalScaleLimits(chart, state);
@@ -134,6 +157,7 @@ export function resetZoom(chart, transition = 'default') {
134157
});
135158
chart.update(transition);
136159

160+
// @ts-expect-error args not assignable to unknown[]
137161
call(state.options.zoom.onZoomComplete, [{chart}]);
138162
}
139163

@@ -146,6 +170,9 @@ function getOriginalRange(state, scaleId) {
146170
return valueOrDefault(max.options, max.scale) - valueOrDefault(min.options, min.scale);
147171
}
148172

173+
/**
174+
* @param {import('chart.js').Chart} chart The Chart instance
175+
*/
149176
export function getZoomLevel(chart) {
150177
const state = getState(chart);
151178
let min = 1;
@@ -178,6 +205,12 @@ function panScale(scale, delta, limits, state) {
178205
}
179206
}
180207

208+
/**
209+
* @param {import('chart.js').Chart} chart The Chart instance
210+
* @param {import('../types').PanAmount} delta
211+
* @param {import('chart.js').Scale[]} [enabledScales]
212+
* @param {import('chart.js').UpdateMode} [transition]
213+
*/
181214
export function pan(chart, delta, enabledScales, transition = 'none') {
182215
const {x = 0, y = 0} = typeof delta === 'number' ? {x: delta, y: delta} : delta;
183216
const state = getState(chart);
@@ -189,6 +222,7 @@ export function pan(chart, delta, enabledScales, transition = 'none') {
189222
const xEnabled = x !== 0;
190223
const yEnabled = y !== 0;
191224

225+
// @ts-expect-error No overload matches this call
192226
each(enabledScales || chart.scales, function(scale) {
193227
if (scale.isHorizontal() && xEnabled) {
194228
panScale(scale, x, limits, state);
@@ -199,9 +233,13 @@ export function pan(chart, delta, enabledScales, transition = 'none') {
199233

200234
chart.update(transition);
201235

236+
// @ts-expect-error args not assignable to unknown[]
202237
call(onPan, [{chart}]);
203238
}
204239

240+
/**
241+
* @param {import('chart.js').Chart} chart The Chart instance
242+
*/
205243
export function getInitialScaleBounds(chart) {
206244
const state = getState(chart);
207245
storeOriginalScaleLimits(chart, state);
@@ -214,6 +252,9 @@ export function getInitialScaleBounds(chart) {
214252
return scaleBounds;
215253
}
216254

255+
/**
256+
* @param {import('chart.js').Chart} chart The Chart instance
257+
*/
217258
export function getZoomedScaleBounds(chart) {
218259
const state = getState(chart);
219260
const scaleBounds = {};
@@ -224,6 +265,9 @@ export function getZoomedScaleBounds(chart) {
224265
return scaleBounds;
225266
}
226267

268+
/**
269+
* @param {import('chart.js').Chart} chart The Chart instance
270+
*/
227271
export function isZoomedOrPanned(chart) {
228272
const scaleBounds = getInitialScaleBounds(chart);
229273
for (const scaleId of Object.keys(chart.scales)) {
@@ -241,6 +285,9 @@ export function isZoomedOrPanned(chart) {
241285
return false;
242286
}
243287

288+
/**
289+
* @param {import('chart.js').Chart} chart The Chart instance
290+
*/
244291
export function isZoomingOrPanning(chart) {
245292
const state = getState(chart);
246293
return state.panning || state.dragging;

src/hammer.js

+1-1
Original file line numberDiff line numberDiff line change
@@ -59,7 +59,7 @@ function handlePinch(chart, state, e) {
5959
}
6060
};
6161

62-
zoom(chart, amount);
62+
zoom(chart, amount, 'zoom', 'pinch');
6363

6464
// Keep track of overall scale
6565
state.scale = e.scale;

src/handlers.js

+17-2
Original file line numberDiff line numberDiff line change
@@ -68,11 +68,18 @@ function getPointPosition(event, chart) {
6868
return getRelativePosition(event, chart);
6969
}
7070

71+
/**
72+
* @param {import('chart.js').Chart} chart
73+
* @param {*} event
74+
* @param {import('../types/options').ZoomOptions} zoomOptions
75+
*/
7176
function zoomStart(chart, event, zoomOptions) {
7277
const {onZoomStart, onZoomRejected} = zoomOptions;
7378
if (onZoomStart) {
7479
const point = getPointPosition(event, chart);
80+
// @ts-expect-error args not assignable to unknown[]
7581
if (call(onZoomStart, [{chart, event, point}]) === false) {
82+
// @ts-expect-error args not assignable to unknown[]
7683
call(onZoomRejected, [{chart, event}]);
7784
return false;
7885
}
@@ -93,6 +100,7 @@ export function mouseDown(chart, event) {
93100
keyPressed(getModifierKey(panOptions), event) ||
94101
keyNotPressed(getModifierKey(zoomOptions.drag), event)
95102
) {
103+
// @ts-expect-error args not assignable to unknown[]
96104
return call(zoomOptions.onZoomRejected, [{chart, event}]);
97105
}
98106

@@ -189,16 +197,23 @@ export function mouseUp(chart, event) {
189197
return;
190198
}
191199

192-
zoomRect(chart, {x: rect.left, y: rect.top}, {x: rect.right, y: rect.bottom}, 'zoom');
200+
zoomRect(chart, {x: rect.left, y: rect.top}, {x: rect.right, y: rect.bottom}, 'zoom', 'drag');
193201

194202
state.dragging = false;
195203
state.filterNextClick = true;
204+
// @ts-expect-error args not assignable to unknown[]
196205
call(onZoomComplete, [{chart}]);
197206
}
198207

208+
/**
209+
* @param {import('chart.js').Chart} chart
210+
* @param {*} event
211+
* @param {import('../types/options').ZoomOptions} zoomOptions
212+
*/
199213
function wheelPreconditions(chart, event, zoomOptions) {
200214
// Before preventDefault, check if the modifier key required and pressed
201215
if (keyNotPressed(getModifierKey(zoomOptions.wheel), event)) {
216+
// @ts-expect-error args not assignable to unknown[]
202217
call(zoomOptions.onZoomRejected, [{chart, event}]);
203218
return;
204219
}
@@ -239,7 +254,7 @@ export function wheel(chart, event) {
239254
}
240255
};
241256

242-
zoom(chart, amount);
257+
zoom(chart, amount, 'zoom', 'wheel');
243258

244259
call(onZoomComplete, [{chart}]);
245260
}

src/state.js

+12
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,17 @@
1+
/**
2+
* @typedef {import("chart.js").Chart} Chart
3+
* @typedef {{originalScaleLimits: any; updatedScaleLimits: any; handlers: any; panDelta: any; dragging: boolean; panning: boolean; options?: import("../types/options").ZoomPluginOptions, dragStart?: any, dragEnd?: any, filterNextClick?: boolean}} ZoomPluginState
4+
*/
5+
6+
/**
7+
* @type WeakMap<Chart, ZoomPluginState>
8+
*/
19
const chartStates = new WeakMap();
210

11+
/**
12+
* @param {import("chart.js").Chart} chart
13+
* @returns {ZoomPluginState}
14+
*/
315
export function getState(chart) {
416
let state = chartStates.get(chart);
517
if (!state) {

test/specs/api.spec.js

+1-1
Original file line numberDiff line numberDiff line change
@@ -265,7 +265,7 @@ describe('api', function() {
265265

266266
chart.zoomScale('x', {min: 2, max: 10}, 'default');
267267

268-
expect(zoomSpy).toHaveBeenCalledWith({chart});
268+
expect(zoomSpy).toHaveBeenCalledWith({chart, trigger: 'api'});
269269
});
270270
});
271271

test/specs/zoom.drag.spec.js

+1-1
Original file line numberDiff line numberDiff line change
@@ -553,7 +553,7 @@ describe('zoom with drag', function() {
553553
// expect(chart.isZoomingOrPanning()).toBe(false);
554554

555555
expect(startSpy).toHaveBeenCalled();
556-
expect(zoomSpy).toHaveBeenCalled();
556+
expect(zoomSpy).toHaveBeenCalledWith({chart, trigger: 'drag'});
557557
});
558558

559559
it('should call onZoomRejected when onZoomStart returns false', function() {

test/specs/zoom.wheel.spec.js

+9-32
Original file line numberDiff line numberDiff line change
@@ -373,8 +373,9 @@ describe('zoom with wheel', function() {
373373
});
374374

375375
describe('events', function() {
376-
it('should call onZoomStart', function() {
377-
const startSpy = jasmine.createSpy('started');
376+
it('should call onZoomStart, onZoom and onZoomComplete', function(done) {
377+
const startSpy = jasmine.createSpy('start');
378+
const zoomSpy = jasmine.createSpy('zoom');
378379
const chart = window.acquireChart({
379380
type: 'scatter',
380381
data,
@@ -386,7 +387,9 @@ describe('zoom with wheel', function() {
386387
enabled: true,
387388
},
388389
mode: 'xy',
389-
onZoomStart: startSpy
390+
onZoomStart: startSpy,
391+
onZoom: zoomSpy,
392+
onZoomComplete: () => done()
390393
}
391394
}
392395
}
@@ -397,8 +400,11 @@ describe('zoom with wheel', function() {
397400
y: chart.scales.y.getPixelForValue(1.1),
398401
deltaY: 1
399402
};
403+
400404
jasmine.triggerWheelEvent(chart, wheelEv);
405+
401406
expect(startSpy).toHaveBeenCalled();
407+
expect(zoomSpy).toHaveBeenCalledWith({chart, trigger: 'wheel'});
402408
expect(chart.scales.x.min).not.toBe(1);
403409
});
404410

@@ -467,34 +473,5 @@ describe('zoom with wheel', function() {
467473
expect(rejectSpy).toHaveBeenCalled();
468474
expect(chart.scales.x.min).toBe(1);
469475
});
470-
471-
it('should call onZoomComplete', function(done) {
472-
const chart = window.acquireChart({
473-
type: 'scatter',
474-
data,
475-
options: {
476-
plugins: {
477-
zoom: {
478-
zoom: {
479-
wheel: {
480-
enabled: true,
481-
},
482-
mode: 'xy',
483-
onZoomComplete(ctx) {
484-
expect(ctx.chart.scales.x.min).not.toBe(1);
485-
done();
486-
}
487-
}
488-
}
489-
}
490-
}
491-
});
492-
const wheelEv = {
493-
x: chart.scales.x.getPixelForValue(1.5),
494-
y: chart.scales.y.getPixelForValue(1.1),
495-
deltaY: 1
496-
};
497-
jasmine.triggerWheelEvent(chart, wheelEv);
498-
});
499476
});
500477
});

types/index.d.ts

+4-3
Original file line numberDiff line numberDiff line change
@@ -2,11 +2,12 @@ import { Plugin, ChartType, Chart, Scale, UpdateMode, ScaleTypeRegistry, ChartTy
22
import { LimitOptions, ZoomPluginOptions } from './options';
33

44
type Point = { x: number, y: number };
5-
type ZoomAmount = number | Partial<Point> & { focalPoint?: Point };
6-
type PanAmount = number | Partial<Point>;
7-
type ScaleRange = { min: number, max: number };
85
type DistributiveArray<T> = [T] extends [unknown] ? Array<T> : never
96

7+
export type PanAmount = number | Partial<Point>;
8+
export type ScaleRange = { min: number, max: number };
9+
export type ZoomAmount = number | Partial<Point> & { focalPoint?: Point };
10+
1011
declare module 'chart.js' {
1112
// eslint-disable-next-line @typescript-eslint/no-unused-vars
1213
interface PluginOptionsByType<TType extends ChartType> {

0 commit comments

Comments
 (0)