diff --git a/docs/guide/developers.md b/docs/guide/developers.md index 80f48114..41b9d053 100644 --- a/docs/guide/developers.md +++ b/docs/guide/developers.md @@ -44,7 +44,7 @@ Returns whether the chart has been zoomed or panned - i.e. whether the initial s ## Custom Scales -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. +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`, `zoomRectFunctions`, and `panFunctions` members. These objects are indexed by scale types (scales' `id` members) and give optional handlers for zoom and pan functionality. ```js import {Scale} from 'chart.js'; @@ -57,17 +57,22 @@ MyScale.id = 'myScale'; MyScale.defaults = defaultConfigObject; zoomPlugin.zoomFunctions.myScale = (scale, zoom, center, limits) => false; +zoomPlugin.zoomRectFunctions.myScale = (scale, from, to, limits) => false; zoomPlugin.panFunctions.myScale = (scale, delta, limits) => false; +// zoomRectFunctions can normally be omitted, since zooming by specific pixel +// coordinates rarely needs special handling. ``` -The zoom and pan functions take the following arguments: +The zoom, zoomRect, and pan functions take the following arguments: | Name | Type | For | Description | ---- | ---- | --- | ---------- | `scale` | `Scale` | Zoom, Pan | The custom scale instance (usually derived from `Chart.Scale`) | `zoom` | `number` | Zoom | The zoom fraction; 1.0 is unzoomed, 0.5 means zoomed in to 50% of the original area, etc. | `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. +| `from` | `number` | ZoomRect | Pixel coordinate of the start of the zoomRect operation. +| `to` | `number` | ZoomRect | Pixel coordinate of the end of the zoomRect operation. | `delta` | `number` | Pan | Pixel amount to pan by | `limits` | [Limits](./options#limits) | Zoom, Pan | Zoom and pan limits (from chart options) -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). +For examples, see chartjs-plugin-zoom's [default zoomFunctions, zoomRectFunctions, and panFunctions handling for standard Chart.js axes](https://github.com/chartjs/chartjs-plugin-zoom/blob/v1.0.1/src/scale.types.js#L128). diff --git a/src/core.js b/src/core.js index c065d39a..20116767 100644 --- a/src/core.js +++ b/src/core.js @@ -1,5 +1,5 @@ import {each, callback as call, sign, valueOrDefault} from 'chart.js/helpers'; -import {panFunctions, updateRange, zoomFunctions} from './scale.types'; +import {panFunctions, updateRange, zoomFunctions, zoomRectFunctions} from './scale.types'; import {getState} from './state'; import {directionEnabled, getEnabledScalesByPoint} from './utils'; @@ -43,6 +43,11 @@ function doZoom(scale, amount, center, limits) { call(fn, [scale, amount, center, limits]); } +function doZoomRect(scale, amount, from, to, limits) { + const fn = zoomRectFunctions[scale.type] || zoomRectFunctions.default; + call(fn, [scale, amount, from, to, limits]); +} + function getCenter(chart) { const ca = chart.chartArea; return { @@ -80,15 +85,6 @@ export function zoom(chart, amount, transition = 'none') { call(zoomOptions.onZoom, [{chart}]); } -function getRange(scale, pixel0, pixel1) { - const v0 = scale.getValueForPixel(pixel0); - const v1 = scale.getValueForPixel(pixel1); - return { - min: Math.min(v0, v1), - max: Math.max(v0, v1) - }; -} - export function zoomRect(chart, p0, p1, transition = 'none') { const state = getState(chart); const {options: {limits, zoom: zoomOptions}} = state; @@ -100,9 +96,9 @@ export function zoomRect(chart, p0, p1, transition = 'none') { each(chart.scales, function(scale) { if (scale.isHorizontal() && xEnabled) { - updateRange(scale, getRange(scale, p0.x, p1.x), limits, true); + doZoomRect(scale, p0.x, p1.x, limits); } else if (!scale.isHorizontal() && yEnabled) { - updateRange(scale, getRange(scale, p0.y, p1.y), limits, true); + doZoomRect(scale, p0.y, p1.y, limits); } }); diff --git a/src/index.esm.js b/src/index.esm.js index f75af00a..3ff12100 100644 --- a/src/index.esm.js +++ b/src/index.esm.js @@ -1,4 +1,4 @@ import plugin from './plugin'; export default plugin; -export {pan, zoom, zoomScale, resetZoom} from './core'; +export {pan, zoom, zoomRect, zoomScale, resetZoom} from './core'; diff --git a/src/plugin.js b/src/plugin.js index ad4a991a..786a13d6 100644 --- a/src/plugin.js +++ b/src/plugin.js @@ -1,8 +1,8 @@ import Hammer from 'hammerjs'; import {addListeners, computeDragRect, removeListeners} from './handlers'; import {startHammer, stopHammer} from './hammer'; -import {pan, zoom, resetZoom, zoomScale, getZoomLevel, getInitialScaleBounds, isZoomedOrPanned} from './core'; -import {panFunctions, zoomFunctions} from './scale.types'; +import {pan, zoom, resetZoom, zoomScale, getZoomLevel, getInitialScaleBounds, isZoomedOrPanned, zoomRect} from './core'; +import {panFunctions, zoomFunctions, zoomRectFunctions} from './scale.types'; import {getState, removeState} from './state'; import {version} from '../package.json'; @@ -53,6 +53,7 @@ export default { chart.pan = (delta, panScales, transition) => pan(chart, delta, panScales, transition); chart.zoom = (args, transition) => zoom(chart, args, transition); + chart.zoomRect = (p0, p1, transition) => zoomRect(chart, p0, p1, transition); chart.zoomScale = (id, range, transition) => zoomScale(chart, id, range, transition); chart.resetZoom = (transition) => resetZoom(chart, transition); chart.getZoomLevel = () => getZoomLevel(chart); @@ -107,6 +108,6 @@ export default { }, panFunctions, - - zoomFunctions + zoomFunctions, + zoomRectFunctions, }; diff --git a/src/scale.types.js b/src/scale.types.js index 97b14e0a..18f68cff 100644 --- a/src/scale.types.js +++ b/src/scale.types.js @@ -29,6 +29,15 @@ function getLimit(state, scale, scaleLimits, prop, fallback) { return valueOrDefault(limit, fallback); } +function getRange(scale, pixel0, pixel1) { + const v0 = scale.getValueForPixel(pixel0); + const v1 = scale.getValueForPixel(pixel1); + return { + min: Math.min(v0, v1), + max: Math.max(v0, v1) + }; +} + export function updateRange(scale, {min, max}, limits, zoom = false) { const state = getState(scale.chart); const {id, axis, options: scaleOpts} = scale; @@ -72,6 +81,10 @@ function zoomNumericalScale(scale, zoom, center, limits) { return updateRange(scale, newRange, limits, true); } +function zoomRectNumericalScale(scale, from, to, limits) { + updateRange(scale, getRange(scale, from, to), limits, true); +} + const integerChange = (v) => v === 0 || isNaN(v) ? 0 : v < 0 ? Math.min(Math.round(v), -1) : Math.max(Math.round(v), 1); function existCategoryFromMaxZoom(scale) { @@ -158,6 +171,10 @@ export const zoomFunctions = { default: zoomNumericalScale, }; +export const zoomRectFunctions = { + default: zoomRectNumericalScale, +}; + export const panFunctions = { category: panCategoryScale, default: panNumericalScale, diff --git a/test/specs/api.spec.js b/test/specs/api.spec.js index 46f16720..b11ef5e5 100644 --- a/test/specs/api.spec.js +++ b/test/specs/api.spec.js @@ -5,6 +5,7 @@ describe('api', function() { expect(typeof chart.pan).toBe('function'); expect(typeof chart.zoom).toBe('function'); expect(typeof chart.zoomScale).toBe('function'); + expect(typeof chart.zoomRect).toBe('function'); expect(typeof chart.resetZoom).toBe('function'); expect(typeof chart.getZoomLevel).toBe('function'); expect(typeof chart.getInitialScaleBounds).toBe('function'); diff --git a/test/specs/module.spec.js b/test/specs/module.spec.js index 456939cf..ff992f79 100644 --- a/test/specs/module.spec.js +++ b/test/specs/module.spec.js @@ -7,8 +7,9 @@ describe('module', function() { expect(window.ChartZoom.id).toBe('zoom'); }); - it ('should expose zoomFunctions and panFunctions', function() { + it ('should expose zoomFunctions, zoomRectFunctions, and panFunctions', function() { expect(window.ChartZoom.zoomFunctions instanceof Object).toBe(true); + expect(window.ChartZoom.zoomRectFunctions instanceof Object).toBe(true); expect(window.ChartZoom.panFunctions instanceof Object).toBe(true); }); diff --git a/types/index.d.ts b/types/index.d.ts index 0ed7c65e..147f6503 100644 --- a/types/index.d.ts +++ b/types/index.d.ts @@ -21,6 +21,7 @@ declare module 'chart.js' { interface Chart, TLabel = unknown> { pan(pan: PanAmount, scales?: Scale[], mode?: UpdateMode): void; zoom(zoom: ZoomAmount, mode?: UpdateMode): void; + zoomRect(p0: Point, p1: Point, mode?: UpdateMode): void; zoomScale(id: string, range: ScaleRange, mode?: UpdateMode): void; resetZoom(mode?: UpdateMode): void; getZoomLevel(): number; @@ -30,6 +31,7 @@ declare module 'chart.js' { } export type ZoomFunction = (scale: Scale, zoom: number, center: Point, limits: LimitOptions) => boolean; +export type ZoomRectFunction = (scale: Scale, from: number, to: number, limits: LimitOptions) => boolean; export type PanFunction = (scale: Scale, delta: number, limits: LimitOptions) => boolean; type ScaleFunctions = { @@ -40,6 +42,7 @@ type ScaleFunctions = { declare const Zoom: Plugin & { zoomFunctions: ScaleFunctions; + zoomRectFunctions: ScaleFunctions; panFunctions: ScaleFunctions; }; @@ -47,6 +50,7 @@ export default Zoom; export function pan(chart: Chart, amount: PanAmount, scales?: Scale[], mode?: UpdateMode): void; export function zoom(chart: Chart, amount: ZoomAmount, mode?: UpdateMode): void; +export function zoomRect(chart: Chart, p0: Point, p1: Point, mode?: UpdateMode): void; 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;