chartjs/chartjs-plugin-zoom

Drag to the outside of the chart doesn't work correctly

lukicenturi opened this issue · 2 comments

Drag to the outside of the chart doesn't work correctly
It's only happen in 2.0.1. I downgrade to 2.0.0, it works fine.

image
In 2.0.1, it seems that it calculated the length between mousedown and mouseup, so it shows longer range than expected.
(I expect the range starts on the mousedown point, till the end of the chart)

I see this same issue (with chart.js 3.9.1 and react 17.0.2) and can confirm that downgrading from 2.0.1 to 2.0.0 fixes it.

I believe the cause is that getRelativePosition (called in computeDragRect, which is called in mouseUp) is returning a position relative to whatever element the mouse was over when the endPointEvent mouseup happened rather than the position relative to the chart canvas.

export function computeDragRect(chart, mode, beginPointEvent, endPointEvent) {
const xEnabled = directionEnabled(mode, 'x', chart);
const yEnabled = directionEnabled(mode, 'y', chart);
let {top, left, right, bottom, width: chartWidth, height: chartHeight} = chart.chartArea;
const beginPoint = getRelativePosition(beginPointEvent, chart);
const endPoint = getRelativePosition(endPointEvent, chart);
if (xEnabled) {
left = Math.min(beginPoint.x, endPoint.x);
right = Math.max(beginPoint.x, endPoint.x);
}
if (yEnabled) {
top = Math.min(beginPoint.y, endPoint.y);
bottom = Math.max(beginPoint.y, endPoint.y);
}
const width = right - left;
const height = bottom - top;
return {
left,
top,
right,
bottom,
width,
height,
zoomX: xEnabled && width ? 1 + ((chartWidth - width) / chartWidth) : 1,
zoomY: yEnabled && height ? 1 + ((chartHeight - height) / chartHeight) : 1
};
}
export function mouseUp(chart, event) {
const state = getState(chart);
if (!state.dragStart) {
return;
}
removeHandler(chart, 'mousemove');
const {mode, onZoomComplete, drag: {threshold = 0}} = state.options.zoom;
const rect = computeDragRect(chart, mode, state.dragStart, event);

I was just about to create an issue for this. Unsure if there is active development on this plugin, but I think you should follow the chartjs core implementation of getRelativePosition and write your own.

The reason theirs doesn't work for you is because they seem to assume that any events passed to that method have an equivalent target and currentTarget, i.e. they all originate from the canvas. This isn't the case for the zoom plugin because the mouseUp listener for drag detection is attached to the canvas.ownerDocument.

This gets you into situations where, when dragging out of the canvas, your start and end points get flipped. For example:

  • I have a 500px height canvas
  • I'm trying to select from the middle (250), to the end (500)
  • I drag from the middle to one pixel outside of the canvas, 1px into some sibling element
  • e.target is now that sibling element, so offsetY becomes 1
  • I now have start.y = 250 and end.y = 1 which just gets flipped further down the call chain
  • on mouseUp I've selected selected the first half of the chart (1-250) instead of the last half (250-500) like I intended.

Another method to get canvas position would be to, instead of relying on offsetY/X, use canvas.getBoundingClientRect(). Then just subtract clientX/Y by left/top.

You do need to clamp though, because while start and end won't flip accidentally, you could get an end position that is greater than the actual height or width of the canvas. In practice this just means that you will zoom slightly further past the current scale.