From 7ec113a1375c77ade9f48ce98ac7c0b903e99b60 Mon Sep 17 00:00:00 2001 From: Josh Kelley Date: Thu, 1 Sep 2022 11:30:32 -0400 Subject: [PATCH] Replace overScaleMode with more flexible scaleMode (#658) * Spelling * Implement new scaleMode property * Add tests and a deprecation warning * Fix wording --- docs/guide/options.md | 6 +- docs/samples/wheel/over-scale-mode.md | 4 +- samples/zoom-separately.html | 8 +-- src/core.js | 13 ++--- src/hammer.js | 4 +- src/handlers.js | 2 +- src/plugin.js | 4 ++ src/utils.js | 45 ++++++++++---- test/specs/zoom.spec.js | 84 +++++++++++++++++++++++++++ types/options.d.ts | 10 +++- 10 files changed, 149 insertions(+), 31 deletions(-) diff --git a/docs/guide/options.md b/docs/guide/options.md index f45a50e0..2eedf5c6 100644 --- a/docs/guide/options.md +++ b/docs/guide/options.md @@ -35,7 +35,8 @@ const chart = new Chart('id', { | `enabled` | `boolean` | `false` | Enable panning | `mode` | `'x'`\|`'y'`\|`'xy'` | `'xy'` | Allowed panning directions | `modifierKey` | `'ctrl'`\|`'alt'`\|`'shift'`\|`'meta'` | `null` | Modifier key required for panning with mouse -| `overScaleMode` | `'x'`\|`'y'`\|`'xy'` | `undefined` | Which of the enabled panning directions should only be available when the mouse cursor is over a scale for that axis +| `scaleMode` | `'x'`\|`'y'`\|`'xy'` | `undefined` | Enable panning over a scale for that axis (regardless of mode) +| `overScaleMode` | `'x'`\|`'y'`\|`'xy'` | `undefined` | Enable panning over a scale for that axis (but only if mode is also enabled), and disables panning along that axis otherwise. Deprecated. | `threshold` | `number` | `10` | Minimal pan distance required before actually applying pan ### Pan Events @@ -57,7 +58,8 @@ const chart = new Chart('id', { | `drag` | [`DragOptions`](#drag-options) | | Options of the drag-to-zoom behavior | `pinch` | [`PinchOptions`](#pinch-options) | | Options of the pinch behavior | `mode` | `'x'`\|`'y'`\|`'xy'` | `'xy'` | Allowed zoom directions -| `overScaleMode` | `'x'`\|`'y'`\|`'xy'` | `undefined` | Which of the enabled zooming directions should only be available when the mouse cursor is over a scale for that axis +| `scaleMode` | `'x'`\|`'y'`\|`'xy'` | `undefined` | Which of the enabled zooming directions should only be available when the mouse cursor is over a scale for that axis +| `overScaleMode` | `'x'`\|`'y'`\|`'xy'` | `undefined` | Allowed zoom directions when the mouse cursor is over a scale for that axis (but only if mode is also enabled), and disables zooming along that axis otherwise. Deprecated; use `scaleMode` instead. #### Wheel options diff --git a/docs/samples/wheel/over-scale-mode.md b/docs/samples/wheel/over-scale-mode.md index d3b707d5..685791d7 100644 --- a/docs/samples/wheel/over-scale-mode.md +++ b/docs/samples/wheel/over-scale-mode.md @@ -62,12 +62,12 @@ const zoomOptions = { enabled: true, }, mode: 'xy', - overScaleMode: 'xy', + scaleMode: 'xy', }, pan: { enabled: true, mode: 'xy', - overScaleMode: 'xy', + scaleMode: 'xy', } }; // diff --git a/samples/zoom-separately.html b/samples/zoom-separately.html index 84e2ca0a..d5d20dce 100644 --- a/samples/zoom-separately.html +++ b/samples/zoom-separately.html @@ -100,8 +100,8 @@ zoom: { pan: { enabled: true, - mode: 'xy', - overScaleMode: 'y' + mode: 'x', + scaleMode: 'y' }, zoom: { wheel: { @@ -110,8 +110,8 @@ pinch: { enabled: true, }, - mode: 'xy', - overScaleMode: 'y' + mode: 'x', + scaleMode: 'y' } } } diff --git a/src/core.js b/src/core.js index 79b4ad4c..c065d39a 100644 --- a/src/core.js +++ b/src/core.js @@ -60,13 +60,12 @@ export function zoom(chart, amount, transition = 'none') { const {x = 1, y = 1, focalPoint = getCenter(chart)} = typeof amount === 'number' ? {x: amount, y: amount} : amount; const state = getState(chart); const {options: {limits, zoom: zoomOptions}} = state; - const {mode = 'xy', overScaleMode} = zoomOptions || {}; storeOriginalScaleLimits(chart, state); - const xEnabled = x !== 1 && directionEnabled(mode, 'x', chart); - const yEnabled = y !== 1 && directionEnabled(mode, 'y', chart); - const enabledScales = overScaleMode && getEnabledScalesByPoint(overScaleMode, focalPoint, chart); + const xEnabled = x !== 1; + const yEnabled = y !== 1; + const enabledScales = getEnabledScalesByPoint(zoomOptions, focalPoint, chart); each(enabledScales || chart.scales, function(scale) { if (scale.isHorizontal() && xEnabled) { @@ -182,12 +181,12 @@ export function pan(chart, delta, enabledScales, transition = 'none') { const {x = 0, y = 0} = typeof delta === 'number' ? {x: delta, y: delta} : delta; const state = getState(chart); const {options: {pan: panOptions, limits}} = state; - const {mode = 'xy', onPan} = panOptions || {}; + const {onPan} = panOptions || {}; storeOriginalScaleLimits(chart, state); - const xEnabled = x !== 0 && directionEnabled(mode, 'x', chart); - const yEnabled = y !== 0 && directionEnabled(mode, 'y', chart); + const xEnabled = x !== 0; + const yEnabled = y !== 0; each(enabledScales || chart.scales, function(scale) { if (scale.isHorizontal() && xEnabled) { diff --git a/src/hammer.js b/src/hammer.js index 273f000d..91b0c6a1 100644 --- a/src/hammer.js +++ b/src/hammer.js @@ -90,7 +90,7 @@ function handlePan(chart, state, e) { } function startPan(chart, state, event) { - const {enabled, overScaleMode, onPanStart, onPanRejected} = state.options.pan; + const {enabled, onPanStart, onPanRejected} = state.options.pan; if (!enabled) { return; } @@ -104,7 +104,7 @@ function startPan(chart, state, event) { return call(onPanRejected, [{chart, event}]); } - state.panScales = overScaleMode && getEnabledScalesByPoint(overScaleMode, point, chart); + state.panScales = getEnabledScalesByPoint(state.options.pan, point, chart); state.delta = {x: 0, y: 0}; clearTimeout(state.panEndTimeout); handlePan(chart, state, event); diff --git a/src/handlers.js b/src/handlers.js index 00a90456..416f720a 100644 --- a/src/handlers.js +++ b/src/handlers.js @@ -133,7 +133,7 @@ function wheelPreconditions(chart, event, zoomOptions) { return; } - // Prevent the event from triggering the default behavior (eg. Content scrolling). + // Prevent the event from triggering the default behavior (e.g. content scrolling). if (event.cancelable) { event.preventDefault(); } diff --git a/src/plugin.js b/src/plugin.js index 26b13abc..ad4a991a 100644 --- a/src/plugin.js +++ b/src/plugin.js @@ -42,6 +42,10 @@ export default { if (Object.prototype.hasOwnProperty.call(options.zoom, 'enabled')) { console.warn('The option `zoom.enabled` is no longer supported. Please use `zoom.wheel.enabled`, `zoom.drag.enabled`, or `zoom.pinch.enabled`.'); } + if (Object.prototype.hasOwnProperty.call(options.zoom, 'overScaleMode') + || Object.prototype.hasOwnProperty.call(options.pan, 'overScaleMode')) { + console.warn('The option `overScaleMode` is deprecated. Please use `scaleMode` instead (and update `mode` as desired).'); + } if (Hammer) { startHammer(chart, options); diff --git a/src/utils.js b/src/utils.js index cce065f7..3d16880c 100644 --- a/src/utils.js +++ b/src/utils.js @@ -22,6 +22,17 @@ export function directionEnabled(mode, dir, chart) { return false; } +function directionsEnabled(mode, chart) { + if (typeof mode === 'function') { + mode = mode({chart}); + } + if (typeof mode === 'string') { + return {x: mode.indexOf('x') !== -1, y: mode.indexOf('y') !== -1}; + } + + return {x: false, y: false}; +} + /** * Debounces calling `fn` for `delay` ms * @param {function} fn - Function to call. No arguments are passed. @@ -37,7 +48,8 @@ export function debounce(fn, delay) { }; } -/** This function use for check what axis now under mouse cursor. +/** + * Checks which axis is under the mouse cursor. * @param {{x: number, y: number}} point - the mouse location * @param {import('chart.js').Chart} [chart] instance of the chart in question * @return {import('chart.js').Scale} @@ -54,27 +66,40 @@ function getScaleUnderPoint({x, y}, chart) { return null; } -/** This function return only one scale whose position is under mouse cursor and which direction is enabled. - * If under mouse hasn't scale, then return all other scales which 'mode' is diffrent with overScaleMode. - * So 'overScaleMode' works as a limiter to scale the user-selected scale (in 'mode') only when the cursor is under the scale, - * and other directions in 'mode' works as before. - * Example: mode = 'xy', overScaleMode = 'y' -> it's means 'x' - works as before, and 'y' only works for one scale when cursor is under it. +/** + * Evaluate the chart's mode, scaleMode, and overScaleMode properties to + * determine which axes are eligible for scaling. * options.overScaleMode can be a function if user want zoom only one scale of many for example. - * @param {string} mode - 'xy', 'x' or 'y' + * @param options - Zoom or pan options * @param {{x: number, y: number}} point - the mouse location * @param {import('chart.js').Chart} [chart] instance of the chart in question * @return {import('chart.js').Scale[]} */ -export function getEnabledScalesByPoint(mode, point, chart) { +export function getEnabledScalesByPoint(options, point, chart) { + const {mode = 'xy', scaleMode, overScaleMode} = options || {}; const scale = getScaleUnderPoint(point, chart); - if (scale && directionEnabled(mode, scale.axis, chart)) { + const enabled = directionsEnabled(mode, chart); + const scaleEnabled = directionsEnabled(scaleMode, chart); + + // Convert deprecated overScaleEnabled to new scaleEnabled. + if (overScaleMode) { + const overScaleEnabled = directionsEnabled(overScaleMode, chart); + for (const axis of ['x', 'y']) { + if (overScaleEnabled[axis]) { + scaleEnabled[axis] = enabled[axis]; + enabled[axis] = false; + } + } + } + + if (scale && scaleEnabled[scale.axis]) { return [scale]; } const enabledScales = []; each(chart.scales, function(scaleItem) { - if (!directionEnabled(mode, scaleItem.axis, chart)) { + if (enabled[scaleItem.axis]) { enabledScales.push(scaleItem); } }); diff --git a/test/specs/zoom.spec.js b/test/specs/zoom.spec.js index 5c62469c..bb76e5c6 100644 --- a/test/specs/zoom.spec.js +++ b/test/specs/zoom.spec.js @@ -550,6 +550,90 @@ describe('zoom', function() { }); }); + describe('with scaleMode = y and mode = xy', function() { + const config = { + type: 'line', + data, + options: { + scales: { + x: { + type: 'linear', + min: 1, + max: 10 + }, + y: { + type: 'linear' + } + }, + plugins: { + zoom: { + zoom: { + wheel: { + enabled: true, + }, + mode: 'xy', + scaleMode: 'y' + } + } + } + } + }; + + describe('Wheel under Y scale', function() { + it('should be applied on Y, but not on X scales.', async function() { + const chart = window.acquireChart(config); + + const scaleX = chart.scales.x; + const scaleY = chart.scales.y; + + const oldMinX = scaleX.options.min; + const oldMaxX = scaleX.options.max; + const oldMinY = scaleY.options.min; + const oldMaxY = scaleY.options.max; + + const wheelEv = { + x: scaleY.left + (scaleY.right - scaleY.left) / 2, + y: scaleY.top + (scaleY.bottom - scaleY.top) / 2, + deltaY: 1 + }; + + await jasmine.triggerWheelEvent(chart, wheelEv); + + expect(scaleX.options.min).toEqual(oldMinX); + expect(scaleX.options.max).toEqual(oldMaxX); + expect(scaleY.options.min).not.toEqual(oldMinY); + expect(scaleY.options.max).not.toEqual(oldMaxY); + }); + }); + + describe('Wheel not under Y scale', function() { + it('should be applied on X and Y scales.', async function() { + const chart = window.acquireChart(config); + + const scaleX = chart.scales.x; + const scaleY = chart.scales.y; + + const oldMinX = scaleX.options.min; + const oldMaxX = scaleX.options.max; + const oldMinY = scaleY.options.min; + const oldMaxY = scaleY.options.max; + + const wheelEv = { + x: scaleX.getPixelForValue(1.5), + y: scaleY.getPixelForValue(1.1), + deltaY: 1 + }; + + await jasmine.triggerWheelEvent(chart, wheelEv); + + expect(scaleX.options.min).not.toEqual(oldMinX); + expect(scaleX.options.max).not.toEqual(oldMaxX); + expect(scaleY.options.min).not.toEqual(oldMinY); + expect(scaleY.options.max).not.toEqual(oldMaxY); + }); + }); + }); + describe('events', function() { describe('wheel', function() { it('should call onZoomStart', function() { diff --git a/types/options.d.ts b/types/options.d.ts index d276e415..6b1f2eb7 100644 --- a/types/options.d.ts +++ b/types/options.d.ts @@ -67,7 +67,7 @@ export interface PinchOptions { export interface ZoomOptions { /** * Zooming directions. Remove the appropriate direction to disable - * Eg. 'y' would only allow zooming in the y direction + * E.g. 'y' would only allow zooming in the y direction * A function that is called as the user is zooming and returns the * available directions can also be used: * mode: function({ chart }) { @@ -91,6 +91,8 @@ export interface ZoomOptions { */ pinch?: PinchOptions; + scaleMode?: Mode | { (chart: Chart): Mode }; + /** @deprecated Use scaleMode instead */ overScaleMode?: Mode | { (chart: Chart): Mode }; /** @@ -122,7 +124,7 @@ export interface PanOptions { /** * Panning directions. Remove the appropriate direction to disable - * Eg. 'y' would only allow panning in the y direction + * E.g. 'y' would only allow panning in the y direction * A function that is called as the user is panning and returns the * available directions can also be used: * mode: function({ chart }) { @@ -136,7 +138,9 @@ export interface PanOptions { */ modifierKey?: Key; - overScaleMode?: Mode | { (char: Chart): Mode }; + scaleMode?: Mode | { (chart: Chart): Mode }; + /** @deprecated Use scaleMode instead */ + overScaleMode?: Mode | { (chart: Chart): Mode }; /** * Minimal pan distance required before actually applying pan