Skip to content

Commit

Permalink
Refactor & export pan and zoom functions
Browse files Browse the repository at this point in the history
  • Loading branch information
kurkle committed Apr 25, 2021
1 parent 78f0386 commit 3220c11
Show file tree
Hide file tree
Showing 13 changed files with 259 additions and 102 deletions.
1 change: 1 addition & 0 deletions docs/.vuepress/config.js
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,7 @@ module.exports = {
'over-scale-mode',
'bar',
'log',
'api',
],
}
}
Expand Down
109 changes: 109 additions & 0 deletions docs/samples/api.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
# API

```js chart-editor
// <block:data:1>
const NUMBER_CFG = {count: 20, min: -100, max: 100};
const data = {
datasets: [{
label: 'My First dataset',
borderColor: Utils.randomColor(0.4),
backgroundColor: Utils.randomColor(0.1),
pointBorderColor: Utils.randomColor(0.7),
pointBackgroundColor: Utils.randomColor(0.5),
pointBorderWidth: 1,
data: Utils.points(NUMBER_CFG),
}, {
label: 'My Second dataset',
borderColor: Utils.randomColor(0.4),
backgroundColor: Utils.randomColor(0.1),
pointBorderColor: Utils.randomColor(0.7),
pointBackgroundColor: Utils.randomColor(0.5),
pointBorderWidth: 1,
data: Utils.points(NUMBER_CFG),
}]
};
// </block:data>

// <block:scales:2>
const scaleOpts = {
reverse: true,
ticks: {
callback: (val, index, ticks) => index === 0 || index === ticks.length - 1 ? null : val,
},
grid: {
borderColor: Utils.randomColor(1),
color: 'rgba( 0, 0, 0, 0.1)',
},
title: {
display: true,
text: (ctx) => ctx.scale.axis + ' axis',
}
};
const scales = {
x: {
position: 'top',
},
y: {
position: 'right',
},
};
Object.keys(scales).forEach(scale => Object.assign(scales[scale], scaleOpts));
// </block:scales>

// <block:config:1>
const config = {
type: 'scatter',
data: data,
options: {
scales: scales,
}
};
// </block:config>

// <block:actions:0>
// Note: changes to these actions are not applied to the buttons.
const actions = [
{
name: 'Zoom +10%',
handler(chart) {
chart.zoom(1.1);
}
}, {
name: 'Zoom -10%',
handler(chart) {
chart.zoom(0.9);
},
}, {
name: 'Zoom x +10%',
handler(chart) {
chart.zoom({x: 1.1});
}
}, {
name: 'Zoom x -10%',
handler(chart) {
chart.zoom({x: 0.9});
},
}, {
name: 'Pan x 100px',
handler(chart) {
chart.pan({x: 100});
}
}, {
name: 'Pan x -100px',
handler(chart) {
chart.pan({x: -100});
},
}, {
name: 'Reset zoom',
handler(chart) {
chart.resetZoom();
}
}
];
// </block:actions>

module.exports = {
actions,
config
};
```
4 changes: 2 additions & 2 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 2 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@
"@rollup/plugin-json": "^4.1.0",
"@typescript-eslint/eslint-plugin": "^4.22.0",
"@typescript-eslint/parser": "^4.22.0",
"chart.js": "^3.1.0",
"chart.js": "3.2.0",
"chartjs-test-utils": "^0.2.2",
"concurrently": "^6.0.2",
"coveralls": "^3.1.0",
Expand Down Expand Up @@ -69,7 +69,7 @@
"vuepress-theme-chartjs": "^0.2.0"
},
"peerDependencies": {
"chart.js": "^3.0.0"
"chart.js": "^3.2.0"
},
"dependencies": {
"hammerjs": "^2.0.8"
Expand Down
84 changes: 39 additions & 45 deletions src/core.js
Original file line number Diff line number Diff line change
Expand Up @@ -23,50 +23,41 @@ function zoomScale(scale, zoom, center, zoomOptions) {
call(fn, [scale, zoom, center, zoomOptions]);
}

function getCenter(chart) {
const ca = chart.chartArea;
return {
x: (ca.left + ca.right) / 2,
y: (ca.top + ca.bottom) / 2,
};
}

/**
* @param chart The chart instance
* @param {number} percentZoomX The zoom percentage in the x direction
* @param {number} percentZoomY The zoom percentage in the y direction
* @param {{x: number, y: number}} focalPoint The x and y coordinates of zoom focal point. The point which doesn't change while zooming. E.g. the location of the mouse cursor when "drag: false"
* @param {object} zoomOptions The zoom options
* @param {string} [whichAxes] `xy`, 'x', or 'y'
* @param {number | {x?: number, y?: number, focalPoint?: {x: number, y: number}}} zoom The zoom percentage or percentages and focal point
* @param {object} [options] The zoom options
* @param {boolean} [useTransition] Whether to use `zoom` transition
*/
export function doZoom(chart, percentZoomX, percentZoomY, focalPoint, zoomOptions, whichAxes, useTransition) {
if (!focalPoint) {
const ca = chart.chartArea;
focalPoint = {
x: (ca.left + ca.right) / 2,
y: (ca.top + ca.bottom) / 2,
};
}
export function doZoom(chart, zoom, options = {}, useTransition) {
const {x = 1, y = 1, focalPoint = getCenter(chart)} = typeof zoom === 'number' ? {x: zoom, y: zoom} : zoom;
const {mode = 'xy', overScaleMode} = options;

storeOriginalScaleLimits(chart);
// Do the zoom here
const zoomMode = typeof zoomOptions.mode === 'function' ? zoomOptions.mode({chart: chart}) : zoomOptions.mode;

// Which axes should be modified when fingers were used.
let _whichAxes;
if (zoomMode === 'xy' && whichAxes !== undefined) {
// based on fingers positions
_whichAxes = whichAxes;
} else {
// no effect
_whichAxes = 'xy';
}

const enabledScales = getEnabledScalesByPoint(zoomOptions, focalPoint.x, focalPoint.y, chart);

const xEnalbed = directionEnabled(mode, 'x', chart);
const yEnabled = directionEnabled(mode, 'y', chart);
const enabledScales = overScaleMode && getEnabledScalesByPoint(overScaleMode, focalPoint, chart);

each(enabledScales || chart.scales, function(scale) {
if (scale.isHorizontal() && directionEnabled(zoomMode, 'x', chart) && directionEnabled(_whichAxes, 'x', chart)) {
zoomScale(scale, percentZoomX, focalPoint, zoomOptions);
} else if (!scale.isHorizontal() && directionEnabled(zoomMode, 'y', chart) && directionEnabled(_whichAxes, 'y', chart)) {
zoomScale(scale, percentZoomY, focalPoint, zoomOptions);
if (x !== 1 && scale.isHorizontal() && xEnalbed) {
zoomScale(scale, x, focalPoint, options);
} else if (y !== 1 && !scale.isHorizontal() && yEnabled) {
zoomScale(scale, y, focalPoint, options);
}
});

chart.update(useTransition ? 'zoom' : 'none');

call(zoomOptions.onZoom, [chart]);
call(options.onZoom, [chart]);
}

export function resetZoom(chart) {
Expand All @@ -90,22 +81,25 @@ function panScale(scale, delta, panOptions) {
call(fn, [scale, delta, panOptions]);
}

export function doPan(chart, deltaX, deltaY, panOptions, panningScales) {
export function doPan(chart, pan, options = {}, enabledScales) {
const {x = 0, y = 0} = typeof pan === 'number' ? {x: pan, y: pan} : pan;
const {mode = 'xy', onPan} = options;

storeOriginalScaleLimits(chart);
if (panOptions.enabled) {
const panMode = typeof panOptions.mode === 'function' ? panOptions.mode({chart}) : panOptions.mode;

each(panningScales || chart.scales, function(scale) {
if (scale.isHorizontal() && directionEnabled(panMode, 'x', chart) && deltaX !== 0) {
panScale(scale, deltaX, panOptions);
} else if (!scale.isHorizontal() && directionEnabled(panMode, 'y', chart) && deltaY !== 0) {
panScale(scale, deltaY, panOptions);
}
});
const xEnalbed = directionEnabled(mode, 'x', chart);
const yEnabled = directionEnabled(mode, 'y', chart);

each(enabledScales || chart.scales, function(scale) {
if (x !== 0 && scale.isHorizontal() && xEnalbed) {
panScale(scale, x, options);
} else if (y !== 0 && !scale.isHorizontal() && yEnabled) {
panScale(scale, y, options);
}
});

chart.update('none');
chart.update('none');

call(panOptions.onPan, [chart]);
}
call(onPan, [chart]);
}

47 changes: 31 additions & 16 deletions src/hammer.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import {callback as call} from 'chart.js/helpers';
import Hammer from 'hammerjs';
import {doPan, doZoom} from './core';
import {getState} from './state';
import {getEnabledScalesByPoint} from './utils';
import {directionEnabled, getEnabledScalesByPoint} from './utils';

function createEnabler(chart) {
const state = getState(chart);
Expand All @@ -26,28 +26,41 @@ function createEnabler(chart) {

function pinchAxes(p0, p1) {
// fingers position difference
const x = Math.abs(p0.clientX - p1.clientX);
const y = Math.abs(p0.clientY - p1.clientY);
const pinchX = Math.abs(p0.clientX - p1.clientX);
const pinchY = Math.abs(p0.clientY - p1.clientY);

// diagonal fingers will change both (xy) axes
const p = x / y;
return p > 0.3 && p < 1.7 ? 'xy' : x > y ? 'x' : 'y';
const p = pinchX / pinchY;
let x, y;
if (p > 0.3 && p < 1.7) {
x = y = true;
} else if (pinchX > pinchY) {
x = true;
} else {
y = true;
}
return {x, y};
}

function handlePinch(chart, state, e) {
if (state.scale) {
const {center, pointers} = e;
// Hammer reports the total scaling. We need the incremental amount
const zoom = 1 / state.scale * e.scale;
const zoomPercent = 1 / state.scale * e.scale;
const rect = e.target.getBoundingClientRect();
const focalPoint = {
x: center.x - rect.left,
y: center.y - rect.top
const pinch = pinchAxes(pointers[0], pointers[1]);
const options = state.options.zoom;
const mode = options.mode;
const zoom = {
x: pinch.x && directionEnabled(mode, 'x', chart) ? zoomPercent : 1,
y: pinch.y && directionEnabled(mode, 'y', chart) ? zoomPercent : 1,
focalPoint: {
x: center.x - rect.left,
y: center.y - rect.top
}
};

const xy = pinchAxes(pointers[0], pointers[1]);

doZoom(chart, zoom, zoom, focalPoint, state.options.zoom, xy);
doZoom(chart, zoom, options);

// Keep track of overall scale
state.scale = e.scale;
Expand All @@ -73,7 +86,7 @@ function handlePan(chart, state, e) {
const delta = state.delta;
if (delta !== null) {
state.panning = true;
doPan(chart, e.deltaX - delta.x, e.deltaY - delta.y, state.options.pan, state.panScales);
doPan(chart, {x: e.deltaX - delta.x, y: e.deltaY - delta.y}, state.options.pan, state.panScales);
state.delta = {x: e.deltaX, y: e.deltaY};
}
}
Expand All @@ -84,10 +97,12 @@ function startPan(chart, state, e) {
return;
}
const rect = e.target.getBoundingClientRect();
const x = e.center.x - rect.left;
const y = e.center.y - rect.top;
const point = {
x: e.center.x - rect.left,
y: e.center.y - rect.top
};

state.panScales = getEnabledScalesByPoint(panOptions, x, y, chart);
state.panScales = getEnabledScalesByPoint(panOptions, point, chart);
state.delta = {x: 0, y: 0};
handlePan(chart, state, e);
}
Expand Down
24 changes: 16 additions & 8 deletions src/handlers.js
Original file line number Diff line number Diff line change
Expand Up @@ -85,11 +85,15 @@ export function mouseUp(chart, event) {
}

const {top, left, width, height} = chart.chartArea;
const focalPoint = {
x: (rect.left - left) / (1 - dragDistanceX / width) + left,
y: (rect.top - top) / (1 - dragDistanceY / height) + top
const zoom = {
x: rect.zoomX,
y: rect.zoomY,
focalPoint: {
x: (rect.left - left) / (1 - dragDistanceX / width) + left,
y: (rect.top - top) / (1 - dragDistanceY / height) + top
}
};
doZoom(chart, rect.zoomX, rect.zoomY, focalPoint, zoomOptions, undefined, true);
doZoom(chart, zoom, zoomOptions, true);

call(zoomOptions.onZoomComplete, [chart]);
}
Expand All @@ -116,12 +120,16 @@ export function wheel(chart, event) {

const rect = event.target.getBoundingClientRect();
const speed = 1 + (event.deltaY >= 0 ? -zoomOptions.speed : zoomOptions.speed);
const center = {
x: event.clientX - rect.left,
y: event.clientY - rect.top
const zoom = {
x: speed,
y: speed,
focalPoint: {
x: event.clientX - rect.left,
y: event.clientY - rect.top
}
};

doZoom(chart, speed, speed, center, zoomOptions);
doZoom(chart, zoom, zoomOptions);

if (onZoomComplete) {
debounce(() => call(onZoomComplete, [{chart}]), 250);
Expand Down
1 change: 1 addition & 0 deletions src/index.esm.js
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import plugin from './plugin';

export default plugin;
export {doPan, doZoom, resetZoom} from './core';
Loading

0 comments on commit 3220c11

Please sign in to comment.