diff --git a/docs/samples/api.md b/docs/samples/api.md index 2524dc5d..bc1c574b 100644 --- a/docs/samples/api.md +++ b/docs/samples/api.md @@ -84,15 +84,21 @@ const actions = [ chart.zoom({x: 0.9}); }, }, { - name: 'Pan x 100px', + name: 'Pan x 100px (anim)', handler(chart) { - chart.pan({x: 100}); + chart.pan({x: 100}, undefined, 'default'); } }, { - name: 'Pan x -100px', + name: 'Pan x -100px (anim)', handler(chart) { - chart.pan({x: -100}); + chart.pan({x: -100}, undefined, 'default'); }, + }, { + name: 'Zoom x: 0..-100, y: 0..100', + handler(chart) { + chart.zoomScale('x', {min: -100, max: 0}, 'default'); + chart.zoomScale('y', {min: 0, max: 100}, 'default'); + } }, { name: 'Reset zoom', handler(chart) { diff --git a/docs/samples/time.md b/docs/samples/time.md index 55cb04e0..3e551132 100644 --- a/docs/samples/time.md +++ b/docs/samples/time.md @@ -106,7 +106,20 @@ const actions = [ handler(chart) { chart.resetZoom(); } + }, { + name: 'Zoom to next week', + handler(chart) { + chart.zoomScale('x', Utils.nextWeek(), 'default'); + chart.update(); + } + }, { + name: 'Zoom to 400-600', + handler(chart) { + chart.zoomScale('y', {min: 400, max: 600}, 'default'); + chart.update(); + } } + ]; module.exports = { diff --git a/docs/scripts/utils.js b/docs/scripts/utils.js index d01d919b..51d3949f 100644 --- a/docs/scripts/utils.js +++ b/docs/scripts/utils.js @@ -1,5 +1,5 @@ import {valueOrDefault} from 'chart.js/helpers'; -import {addHours} from 'date-fns'; +import {addHours, startOfWeek, endOfWeek} from 'date-fns'; // Adapted from http://indiegamr.com/generate-repeatable-random-numbers-in-js/ let _seed = Date.now(); @@ -86,3 +86,12 @@ export function hourlyPoints(config) { const start = new Date().valueOf(); return ys.map((y, i) => ({x: addHours(start, i), y})); } + +export function nextWeek() { + const now = new Date().valueOf(); + const min = startOfWeek(addHours(endOfWeek(now), 24)); + return { + min: +min, + max: +endOfWeek(min) + }; +} diff --git a/src/core.js b/src/core.js index 765e5b1e..8ee79f8b 100644 --- a/src/core.js +++ b/src/core.js @@ -1,5 +1,5 @@ import {each, callback as call} from 'chart.js/helpers'; -import {panFunctions, zoomFunctions} from './scale.types'; +import {panFunctions, updateRange, zoomFunctions} from './scale.types'; import {getState} from './state'; import {directionEnabled, getEnabledScalesByPoint} from './utils'; @@ -18,9 +18,9 @@ function storeOriginalScaleLimits(chart) { return originalScaleLimits; } -function zoomScale(scale, zoom, center, limits) { +function doZoom(scale, amount, center, limits) { const fn = zoomFunctions[scale.type] || zoomFunctions.default; - call(fn, [scale, zoom, center, limits]); + call(fn, [scale, amount, center, limits]); } function getCenter(chart) { @@ -33,11 +33,11 @@ function getCenter(chart) { /** * @param chart The chart instance - * @param {number | {x?: number, y?: number, focalPoint?: {x: number, y: number}}} zoom The zoom percentage or percentages and focal point - * @param {boolean} [useTransition] Whether to use `zoom` transition + * @param {number | {x?: number, y?: number, focalPoint?: {x: number, y: number}}} amount The zoom percentage or percentages and focal point + * @param {string} [transition] Which transiton mode to use. Defaults to 'none' */ -export function doZoom(chart, zoom, useTransition) { - const {x = 1, y = 1, focalPoint = getCenter(chart)} = typeof zoom === 'number' ? {x: zoom, y: zoom} : zoom; +export function zoom(chart, amount, transition = 'none') { + const {x = 1, y = 1, focalPoint = getCenter(chart)} = typeof amount === 'number' ? {x: amount, y: amount} : amount; const {options: {limits, zoom: zoomOptions}} = getState(chart); const {mode = 'xy', overScaleMode} = zoomOptions || {}; @@ -49,18 +49,27 @@ export function doZoom(chart, zoom, useTransition) { each(enabledScales || chart.scales, function(scale) { if (scale.isHorizontal() && xEnabled) { - zoomScale(scale, x, focalPoint, limits); + doZoom(scale, x, focalPoint, limits); } else if (!scale.isHorizontal() && yEnabled) { - zoomScale(scale, y, focalPoint, limits); + doZoom(scale, y, focalPoint, limits); } }); - chart.update(useTransition ? 'zoom' : 'none'); + chart.update(transition); call(zoomOptions.onZoom, [{chart}]); } -export function resetZoom(chart) { + +export function zoomScale(chart, scaleId, range, transition = 'none') { + storeOriginalScaleLimits(chart); + const scale = chart.scales[scaleId]; + updateRange(scale, range, undefined, true); + chart.update(transition); +} + + +export function resetZoom(chart, transition = 'default') { const originalScaleLimits = storeOriginalScaleLimits(chart); each(chart.scales, function(scale) { @@ -73,7 +82,7 @@ export function resetZoom(chart) { delete scaleOptions.max; } }); - chart.update(); + chart.update(transition); } function panScale(scale, delta, limits) { @@ -90,8 +99,8 @@ function panScale(scale, delta, limits) { } } -export function doPan(chart, pan, enabledScales) { - const {x = 0, y = 0} = typeof pan === 'number' ? {x: pan, y: pan} : pan; +export function pan(chart, delta, enabledScales, transition = 'none') { + const {x = 0, y = 0} = typeof delta === 'number' ? {x: delta, y: delta} : delta; const {options: {pan: panOptions, limits}} = getState(chart); const {mode = 'xy', onPan} = panOptions || {}; @@ -108,7 +117,7 @@ export function doPan(chart, pan, enabledScales) { } }); - chart.update('none'); + chart.update(transition); call(onPan, [{chart}]); } diff --git a/src/hammer.js b/src/hammer.js index ed71d35d..078e0e95 100644 --- a/src/hammer.js +++ b/src/hammer.js @@ -1,6 +1,6 @@ import {callback as call} from 'chart.js/helpers'; import Hammer from 'hammerjs'; -import {doPan, doZoom} from './core'; +import {pan, zoom} from './core'; import {getState} from './state'; import {directionEnabled, getEnabledScalesByPoint} from './utils'; @@ -50,7 +50,7 @@ function handlePinch(chart, state, e) { const rect = e.target.getBoundingClientRect(); const pinch = pinchAxes(pointers[0], pointers[1]); const mode = state.options.zoom.mode; - const zoom = { + const amount = { x: pinch.x && directionEnabled(mode, 'x', chart) ? zoomPercent : 1, y: pinch.y && directionEnabled(mode, 'y', chart) ? zoomPercent : 1, focalPoint: { @@ -59,7 +59,7 @@ function handlePinch(chart, state, e) { } }; - doZoom(chart, zoom); + zoom(chart, amount); // Keep track of overall scale state.scale = e.scale; @@ -84,7 +84,7 @@ function handlePan(chart, state, e) { const delta = state.delta; if (delta !== null) { state.panning = true; - doPan(chart, {x: e.deltaX - delta.x, y: e.deltaY - delta.y}, state.panScales); + pan(chart, {x: e.deltaX - delta.x, y: e.deltaY - delta.y}, state.panScales); state.delta = {x: e.deltaX, y: e.deltaY}; } } diff --git a/src/handlers.js b/src/handlers.js index bea39bdd..e9ef411a 100644 --- a/src/handlers.js +++ b/src/handlers.js @@ -1,5 +1,5 @@ import {directionEnabled, debounce} from './utils'; -import {doZoom} from './core'; +import {zoom} from './core'; import {callback as call} from 'chart.js/helpers'; import {getState} from './state'; @@ -85,7 +85,7 @@ export function mouseUp(chart, event) { } const {top, left, width, height} = chart.chartArea; - const zoom = { + const amount = { x: rect.zoomX, y: rect.zoomY, focalPoint: { @@ -93,7 +93,7 @@ export function mouseUp(chart, event) { y: (rect.top - top) / (1 - dragDistanceY / height) + top } }; - doZoom(chart, zoom, true); + zoom(chart, amount, 'zoom'); call(zoomOptions.onZoomComplete, [chart]); } @@ -120,7 +120,7 @@ export function wheel(chart, event) { const rect = event.target.getBoundingClientRect(); const speed = 1 + (event.deltaY >= 0 ? -zoomOptions.speed : zoomOptions.speed); - const zoom = { + const amount = { x: speed, y: speed, focalPoint: { @@ -129,7 +129,7 @@ export function wheel(chart, event) { } }; - doZoom(chart, zoom); + zoom(chart, amount); if (onZoomComplete) { debounce(() => call(onZoomComplete, [{chart}]), 250); diff --git a/src/index.esm.js b/src/index.esm.js index e6361d89..f75af00a 100644 --- a/src/index.esm.js +++ b/src/index.esm.js @@ -1,4 +1,4 @@ import plugin from './plugin'; export default plugin; -export {doPan, doZoom, resetZoom} from './core'; +export {pan, zoom, zoomScale, resetZoom} from './core'; diff --git a/src/plugin.js b/src/plugin.js index 5d59d4a0..371ed40a 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 {doPan, doZoom, resetZoom} from './core'; +import {pan, zoom, resetZoom, zoomScale} from './core'; import {panFunctions, zoomFunctions} from './scale.types'; import {getState, removeState} from './state'; import {version} from '../package.json'; @@ -26,7 +26,7 @@ export default { } }, - start: function(chart, args, options) { + start: function(chart, _args, options) { const state = getState(chart); state.options = options; @@ -34,8 +34,9 @@ export default { startHammer(chart, options); } - chart.pan = (pan, panScales) => doPan(chart, pan, panScales); - chart.zoom = (zoom, useTransition) => doZoom(chart, zoom, useTransition); + chart.pan = (delta, panScales, transition) => pan(chart, delta, panScales, transition); + chart.zoom = (args, transition) => zoom(chart, args, transition); + chart.zoomScale = (id, range, transition) => zoomScale(chart, id, range, transition); chart.resetZoom = () => resetZoom(chart); }, diff --git a/src/scale.types.js b/src/scale.types.js index b3a48c5e..64ce141d 100644 --- a/src/scale.types.js +++ b/src/scale.types.js @@ -12,7 +12,7 @@ function zoomDelta(scale, zoom, center) { }; } -function updateRange(scale, {min, max}, limits, zoom = false) { +export function updateRange(scale, {min, max}, limits, zoom = false) { const {axis, options: scaleOpts} = scale; const {min: minLimit = -Infinity, max: maxLimit = Infinity, minRange = 0} = limits && limits[axis] || {}; const cmin = Math.max(min, minLimit); diff --git a/types/index.d.ts b/types/index.d.ts index bbdc0606..d12549b9 100644 --- a/types/index.d.ts +++ b/types/index.d.ts @@ -1,20 +1,28 @@ -import { Plugin, ChartType, Chart, Scale } from 'chart.js'; +import { Plugin, ChartType, Chart, Scale, UpdateMode } from 'chart.js'; import { DistributiveArray } from 'chart.js/types/utils'; import { ZoomPluginOptions } from './options'; type Point = { x: number, y: number }; type ZoomAmount = number | Partial & { focalPoint?: Point }; type PanAmount = number | Partial; +type ScaleRange = { min: number, max: number }; + declare module 'chart.js' { // eslint-disable-next-line @typescript-eslint/no-unused-vars interface PluginOptionsByType { zoom: ZoomPluginOptions; } + + enum UpdateModeEnum { + zoom = 'zoom' + } + // eslint-disable-next-line @typescript-eslint/no-unused-vars interface Chart, TLabel = unknown> { - pan(pan: PanAmount, scales?: Scale[]): void; - zoom(zoom: ZoomAmount, useTransition?: boolean): void; - resetZoom(): void; + pan(pan: PanAmount, scales?: Scale[], mode?: UpdateMode): void; + zoom(zoom: ZoomAmount, useTransition?: boolean, mode?: UpdateMode): void; + zoomScale(id: string, range: ScaleRange, mode?: UpdateMode): void; + resetZoom(mode?: UpdateMode): void; } } @@ -22,6 +30,7 @@ declare const Zoom: Plugin; export default Zoom; -export function doPan(chart: Chart, pan: PanAmount, scales?: Scale[]): void; -export function doZoom(chart: Chart, zoom: ZoomAmount, useTransition?: boolean): void; -export function resetZoom(chart: Chart): void; +export function pan(chart: Chart, amount: PanAmount, scales?: Scale[], mode?: UpdateMode): void; +export function zoom(chart: Chart, amount: ZoomAmount, mode?: UpdateMode): void; +export function zoomScale(chart: Chart, scaleId: string, range: ScaleRange, mode?: UpdateMode): void; +export function resetZoom(chart: Chart, mode?: UpdateMode): void; diff --git a/types/tests/exports.ts b/types/tests/exports.ts index 91841347..e0a05e48 100644 --- a/types/tests/exports.ts +++ b/types/tests/exports.ts @@ -1,5 +1,5 @@ import { Chart } from 'chart.js'; -import Zoom, { doPan, doZoom, resetZoom } from '../index'; +import Zoom, {pan, zoom, resetZoom } from '../index'; Chart.register(Zoom); Chart.unregister(Zoom); @@ -49,6 +49,6 @@ chart.zoom({ x: 1, y: 1.1, focalPoint: { x: 10, y: 10 } }, true); chart.pan(10); chart.pan({ x: 10, y: 20 }, [chart.scales.x]); -doPan(chart, -42); -doZoom(chart, { x: 1, y: 1.1, focalPoint: { x: 10, y: 10 } }, true); -resetZoom(chart); +pan(chart, -42, undefined, 'zoom'); +zoom(chart, { x: 1, y: 1.1, focalPoint: { x: 10, y: 10 } }, 'zoom'); +resetZoom(chart, 'none');