Skip to content

Commit 8742a24

Browse files
committed
feat(drag): maintainAspectRatio
1 parent 11305aa commit 8742a24

File tree

6 files changed

+197
-11
lines changed

6 files changed

+197
-11
lines changed

docs/.vuepress/config.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -145,6 +145,7 @@ module.exports = {
145145
children: [
146146
'drag/category',
147147
'drag/linear',
148+
'drag/linear-ratio',
148149
'drag/log',
149150
'drag/time',
150151
'drag/timeseries',

docs/guide/options.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -80,7 +80,7 @@ const chart = new Chart('id', {
8080
| [`drawTime`](#draw-time) | `string` | `beforeDatasetsDraw` | When the dragging box is drawn on the chart
8181
| `threshold` | `number` | `0` | Minimal zoom distance required before actually applying zoom
8282
| `modifierKey` | `'ctrl'`\|`'alt'`\|`'shift'`\|`'meta'` | `null` | Modifier key required for drag-to-zoom
83-
83+
| `maintainAspectRatio` | `boolean` | `undefined` | Maintain aspect ratio of the chart
8484

8585
## Draw Time
8686

docs/samples/drag/linear-ratio.md

Lines changed: 112 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,112 @@
1+
# Linear Scales + maintainAspectRatio
2+
3+
Zooming is performed by clicking and selecting an area over the chart with the mouse. Pan is activated by keeping `shift` pressed.
4+
5+
```js chart-editor
6+
// <block:data:1>
7+
const NUMBER_CFG = {count: 20, min: -100, max: 100};
8+
const data = {
9+
datasets: [{
10+
label: 'My First dataset',
11+
borderColor: Utils.randomColor(0.4),
12+
backgroundColor: Utils.randomColor(0.1),
13+
pointBorderColor: Utils.randomColor(0.7),
14+
pointBackgroundColor: Utils.randomColor(0.5),
15+
pointBorderWidth: 1,
16+
data: Utils.points(NUMBER_CFG),
17+
}, {
18+
label: 'My Second dataset',
19+
borderColor: Utils.randomColor(0.4),
20+
backgroundColor: Utils.randomColor(0.1),
21+
pointBorderColor: Utils.randomColor(0.7),
22+
pointBackgroundColor: Utils.randomColor(0.5),
23+
pointBorderWidth: 1,
24+
data: Utils.points(NUMBER_CFG),
25+
}]
26+
};
27+
// </block:data>
28+
29+
// <block:scales:2>
30+
const scaleOpts = {
31+
reverse: true,
32+
grid: {
33+
borderColor: Utils.randomColor(1),
34+
color: 'rgba( 0, 0, 0, 0.1)',
35+
},
36+
title: {
37+
display: true,
38+
text: (ctx) => ctx.scale.axis + ' axis',
39+
}
40+
};
41+
const scales = {
42+
x: {
43+
position: 'top',
44+
},
45+
y: {
46+
position: 'right',
47+
},
48+
};
49+
Object.keys(scales).forEach(scale => Object.assign(scales[scale], scaleOpts));
50+
// </block:scales>
51+
52+
// <block:zoom:0>
53+
const dragColor = Utils.randomColor(0.4);
54+
const zoomOptions = {
55+
pan: {
56+
enabled: true,
57+
mode: 'xy',
58+
modifierKey: 'shift',
59+
},
60+
zoom: {
61+
mode: 'xy',
62+
drag: {
63+
enabled: true,
64+
borderColor: 'rgb(54, 162, 235)',
65+
borderWidth: 1,
66+
backgroundColor: 'rgba(54, 162, 235, 0.3)',
67+
maintainAspectRatio: true,
68+
}
69+
}
70+
};
71+
// </block:zoom>
72+
73+
const zoomStatus = () => zoomOptions.zoom.drag.enabled ? 'enabled' : 'disabled';
74+
75+
// <block:config:1>
76+
const config = {
77+
type: 'scatter',
78+
data: data,
79+
options: {
80+
scales: scales,
81+
plugins: {
82+
zoom: zoomOptions,
83+
title: {
84+
display: true,
85+
position: 'bottom',
86+
text: (ctx) => 'Zoom: ' + zoomStatus()
87+
}
88+
},
89+
}
90+
};
91+
// </block:config>
92+
93+
const actions = [
94+
{
95+
name: 'Toggle zoom',
96+
handler(chart) {
97+
zoomOptions.zoom.drag.enabled = !zoomOptions.zoom.drag.enabled;
98+
chart.update();
99+
}
100+
}, {
101+
name: 'Reset zoom',
102+
handler(chart) {
103+
chart.resetZoom();
104+
}
105+
}
106+
];
107+
108+
module.exports = {
109+
actions,
110+
config,
111+
};
112+
```

src/handlers.js

Lines changed: 34 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -96,7 +96,29 @@ export function mouseDown(chart, event) {
9696
addHandler(chart, window.document, 'keydown', keyDown);
9797
}
9898

99-
export function computeDragRect(chart, mode, beginPointEvent, endPointEvent) {
99+
function applyAspectRatio(rect, aspectRatio, beginPoint) {
100+
let width = rect.right - rect.left;
101+
let height = rect.bottom - rect.top;
102+
const ratio = width / height;
103+
104+
if (ratio > aspectRatio) {
105+
width = height * aspectRatio;
106+
if (beginPoint.x === rect.left) {
107+
rect.right = rect.left + width;
108+
} else {
109+
rect.left = rect.right - width;
110+
}
111+
} else if (ratio < aspectRatio) {
112+
height = width / aspectRatio;
113+
if (beginPoint.y === rect.top) {
114+
rect.bottom = rect.top + height;
115+
} else {
116+
rect.top = rect.bottom - height;
117+
}
118+
}
119+
}
120+
121+
export function computeDragRect(chart, mode, beginPointEvent, endPointEvent, maintainAspectRatio) {
100122
const xEnabled = directionEnabled(mode, 'x', chart);
101123
const yEnabled = directionEnabled(mode, 'y', chart);
102124
let {top, left, right, bottom, width: chartWidth, height: chartHeight} = chart.chartArea;
@@ -113,14 +135,17 @@ export function computeDragRect(chart, mode, beginPointEvent, endPointEvent) {
113135
top = Math.max(0, Math.min(beginPoint.y, endPoint.y));
114136
bottom = Math.min(chart.height, Math.max(beginPoint.y, endPoint.y));
115137
}
116-
const width = right - left;
117-
const height = bottom - top;
138+
const rect = {top, left, right, bottom};
139+
140+
if (xEnabled && yEnabled && maintainAspectRatio) {
141+
applyAspectRatio(rect, chartWidth / chartHeight, beginPoint);
142+
}
143+
144+
const width = rect.right - rect.left;
145+
const height = rect.bottom - rect.top;
118146

119147
return {
120-
left,
121-
top,
122-
right,
123-
bottom,
148+
...rect,
124149
width,
125150
height,
126151
zoomX: xEnabled && width ? 1 + ((chartWidth - width) / chartWidth) : 1,
@@ -135,8 +160,8 @@ export function mouseUp(chart, event) {
135160
}
136161

137162
removeHandler(chart, 'mousemove');
138-
const {mode, onZoomComplete, drag: {threshold = 0}} = state.options.zoom;
139-
const rect = computeDragRect(chart, mode, state.dragStart, event);
163+
const {mode, onZoomComplete, drag: {threshold = 0, maintainAspectRatio}} = state.options.zoom;
164+
const rect = computeDragRect(chart, mode, state.dragStart, event, maintainAspectRatio);
140165
const distanceX = directionEnabled(mode, 'x', chart) ? rect.width : 0;
141166
const distanceY = directionEnabled(mode, 'y', chart) ? rect.height : 0;
142167
const distance = Math.sqrt(distanceX * distanceX + distanceY * distanceY);

src/plugin.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ function draw(chart, caller, options) {
1313
if (dragOptions.drawTime !== caller || !dragEnd) {
1414
return;
1515
}
16-
const {left, top, width, height} = computeDragRect(chart, options.zoom.mode, dragStart, dragEnd);
16+
const {left, top, width, height} = computeDragRect(chart, options.zoom.mode, dragStart, dragEnd, dragOptions.maintainAspectRatio);
1717
const ctx = chart.ctx;
1818

1919
ctx.save();

test/specs/zoom.spec.js

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -253,6 +253,54 @@ describe('zoom', function() {
253253
expect(scaleY.options.min).toBeCloseTo(1.1);
254254
expect(scaleY.options.max).toBeCloseTo(1.7);
255255
});
256+
257+
it('should respect aspectRatio when mode = xy', function() {
258+
chart = window.acquireChart({
259+
type: 'line',
260+
data,
261+
options: {
262+
scales: {
263+
x: {
264+
type: 'linear'
265+
},
266+
y: {
267+
type: 'linear'
268+
}
269+
},
270+
plugins: {
271+
legend: false,
272+
title: false,
273+
zoom: {
274+
zoom: {
275+
drag: {
276+
enabled: true,
277+
maintainAspectRatio: true,
278+
},
279+
mode: 'xy'
280+
}
281+
}
282+
}
283+
}
284+
});
285+
286+
scaleX = chart.scales.x;
287+
scaleY = chart.scales.y;
288+
289+
jasmine.triggerMouseEvent(chart, 'mousedown', {
290+
x: scaleX.getPixelForValue(1.5),
291+
y: scaleY.getPixelForValue(1.1)
292+
});
293+
jasmine.triggerMouseEvent(chart, 'mouseup', {
294+
x: scaleX.getPixelForValue(2.8),
295+
y: scaleY.getPixelForValue(1.7)
296+
});
297+
298+
expect(scaleX.options.min).toBeCloseTo(1.5);
299+
expect(scaleX.options.max).toBeCloseTo(2.1);
300+
expect(scaleY.options.min).toBeCloseTo(1.1);
301+
expect(scaleY.options.max).toBeCloseTo(1.7);
302+
});
303+
256304
});
257305
});
258306

0 commit comments

Comments
 (0)