Skip to content

Commit 7289efd

Browse files
authored
Use static offsets for panning rounded time scale (#474)
1 parent b785842 commit 7289efd

File tree

9 files changed

+166
-9
lines changed

9 files changed

+166
-9
lines changed

karma.conf.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,7 @@ module.exports = function(karma) {
6262
{pattern: 'node_modules/chart.js/dist/chart.js'},
6363
{pattern: 'node_modules/hammer-simulator/index.js'},
6464
{pattern: 'node_modules/hammerjs/hammer.js'},
65+
{pattern: 'node_modules/chartjs-adapter-date-fns/dist/chartjs-adapter-date-fns.bundle.js'},
6566
{pattern: 'test/index.js'},
6667
{pattern: 'src/index.js'},
6768
{pattern: 'test/specs/**/*.js'}

src/core.js

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -77,8 +77,17 @@ export function resetZoom(chart) {
7777
}
7878

7979
function panScale(scale, delta, panOptions, limits) {
80+
const {panDelta} = getState(scale.chart);
81+
// Add possible cumulative delta from previous pan attempts where scale did not change
82+
delta += panDelta[scale.id] || 0;
8083
const fn = panFunctions[scale.type] || panFunctions.default;
81-
call(fn, [scale, delta, panOptions, limits]);
84+
if (call(fn, [scale, delta, panOptions, limits])) {
85+
// The scale changed, reset cumulative delta
86+
panDelta[scale.id] = 0;
87+
} else {
88+
// The scale did not change, store cumulative delta
89+
panDelta[scale.id] = delta;
90+
}
8291
}
8392

8493
export function doPan(chart, pan, enabledScales) {

src/hammer.js

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -80,7 +80,6 @@ function endPinch(chart, state, e) {
8080
}
8181
}
8282

83-
8483
function handlePan(chart, state, e) {
8584
const delta = state.delta;
8685
if (delta !== null) {

src/scale.types.js

Lines changed: 22 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -39,12 +39,14 @@ function updateRange(scale, {min, max}, limits, zoom = false) {
3939
}
4040
scaleOpts.min = min;
4141
scaleOpts.max = max;
42+
// return true if the scale range is changed
43+
return scale.parse(min) !== scale.min || scale.parse(max) !== scale.max;
4244
}
4345

4446
function zoomNumericalScale(scale, zoom, center, limits) {
4547
const delta = zoomDelta(scale, zoom, center);
4648
const newRange = {min: scale.min + delta.min, max: scale.max - delta.max};
47-
updateRange(scale, newRange, limits, true);
49+
return updateRange(scale, newRange, limits, true);
4850
}
4951

5052
const integerChange = (v) => v === 0 || isNaN(v) ? 0 : v < 0 ? Math.min(Math.round(v), -1) : Math.max(Math.round(v), 1);
@@ -61,7 +63,7 @@ function zoomCategoryScale(scale, zoom, center, limits) {
6163
}
6264
const delta = zoomDelta(scale, zoom, center);
6365
const newRange = {min: scale.min + integerChange(delta.min), max: scale.max - integerChange(delta.max)};
64-
updateRange(scale, newRange, limits, true);
66+
return updateRange(scale, newRange, limits, true);
6567
}
6668

6769
const categoryDelta = new WeakMap();
@@ -80,14 +82,27 @@ function panCategoryScale(scale, delta, panOptions, limits) {
8082

8183
categoryDelta.set(scale, minIndex !== scaleMin ? 0 : cumDelta);
8284

83-
updateRange(scale, {min: minIndex, max: maxIndex}, limits);
85+
return updateRange(scale, {min: minIndex, max: maxIndex}, limits);
8486
}
8587

88+
const OFFSETS = {
89+
second: 500, // 500 ms
90+
minute: 30 * 1000, // 30 s
91+
hour: 30 * 60 * 1000, // 30 m
92+
day: 12 * 60 * 60 * 1000, // 12 h
93+
week: 3.5 * 24 * 60 * 60 * 1000, // 3.5 d
94+
month: 15 * 24 * 60 * 60 * 1000, // 15 d
95+
quarter: 60 * 24 * 60 * 60 * 1000, // 60 d
96+
year: 182 * 24 * 60 * 60 * 1000 // 182 d
97+
};
98+
8699
function panNumericalScale(scale, delta, panOptions, limits) {
87-
const {min: prevStart, max: prevEnd} = scale;
88-
const newMin = scale.getValueForPixel(scale.getPixelForValue(prevStart) - delta);
89-
const newMax = scale.getValueForPixel(scale.getPixelForValue(prevEnd) - delta);
90-
updateRange(scale, {min: newMin, max: newMax}, limits);
100+
const {min: prevStart, max: prevEnd, options} = scale;
101+
const round = options.time && options.time.round;
102+
const offset = OFFSETS[round] || 0;
103+
const newMin = scale.getValueForPixel(scale.getPixelForValue(prevStart + offset) - delta);
104+
const newMax = scale.getValueForPixel(scale.getPixelForValue(prevEnd + offset) - delta);
105+
return updateRange(scale, {min: newMin, max: newMax}, limits);
91106
}
92107

93108
export const zoomFunctions = {

src/state.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ export function getState(chart) {
66
state = {
77
originalScaleLimits: {},
88
handlers: {},
9+
panDelta: {}
910
};
1011
chartStates.set(chart, state);
1112
}

test/fixtures/pan/time-day-left.js

Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
const canvas = document.createElement('canvas');
2+
canvas.width = 512;
3+
canvas.height = 512;
4+
const ctx = canvas.getContext('2d');
5+
6+
module.exports = {
7+
config: {
8+
type: 'bar',
9+
data: {
10+
labels: [new Date(2020, 0, 2), new Date(2020, 0, 3), new Date(2020, 0, 4), new Date(2020, 0, 5)],
11+
datasets: [
12+
{
13+
backgroundColor: ['red', 'green', 'blue', 'orange'],
14+
data: [1, 2, 3, 4]
15+
}
16+
]
17+
},
18+
options: {
19+
animation: false,
20+
events: [],
21+
scales: {
22+
y: {
23+
display: false,
24+
max: 5
25+
},
26+
x: {
27+
type: 'time',
28+
min: new Date(2020, 0, 3),
29+
max: new Date(2020, 0, 5),
30+
time: {
31+
unit: 'day',
32+
round: 'day',
33+
}
34+
}
35+
},
36+
plugins: {
37+
legend: false,
38+
zoom: {
39+
pan: {
40+
enabled: true,
41+
mode: 'x',
42+
}
43+
}
44+
}
45+
}
46+
},
47+
options: {
48+
spriteText: true,
49+
run(chart) {
50+
const steps = 4;
51+
const n = Math.sqrt(steps);
52+
const side = 512 / n;
53+
for (let i = 0; i < steps; i++) {
54+
const col = i % n;
55+
const row = Math.floor(i / n);
56+
if (i > 0) {
57+
chart.pan({x: 150});
58+
chart.update();
59+
}
60+
ctx.drawImage(chart.canvas, col * side, row * side, side, side);
61+
}
62+
Chart.helpers.clearCanvas(chart.canvas);
63+
chart.ctx.drawImage(canvas, 0, 0);
64+
}
65+
}
66+
};
16.9 KB
Loading
Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
const canvas = document.createElement('canvas');
2+
canvas.width = 512;
3+
canvas.height = 512;
4+
const ctx = canvas.getContext('2d');
5+
6+
module.exports = {
7+
config: {
8+
type: 'bar',
9+
data: {
10+
labels: [new Date(2020, 0, 2), new Date(2020, 0, 3), new Date(2020, 0, 4), new Date(2020, 0, 5)],
11+
datasets: [
12+
{
13+
backgroundColor: ['red', 'green', 'blue', 'orange'],
14+
data: [1, 2, 3, 4]
15+
}
16+
]
17+
},
18+
options: {
19+
animation: false,
20+
events: [],
21+
scales: {
22+
y: {
23+
display: false,
24+
max: 5
25+
},
26+
x: {
27+
type: 'time',
28+
min: new Date(2020, 0, 1),
29+
max: new Date(2020, 0, 3),
30+
time: {
31+
unit: 'day',
32+
round: 'day',
33+
}
34+
}
35+
},
36+
plugins: {
37+
legend: false,
38+
zoom: {
39+
pan: {
40+
enabled: true,
41+
mode: 'x',
42+
}
43+
}
44+
}
45+
}
46+
},
47+
options: {
48+
spriteText: true,
49+
run(chart) {
50+
const steps = 4;
51+
const n = Math.sqrt(steps);
52+
const side = 512 / n;
53+
for (let i = 0; i < steps; i++) {
54+
const col = i % n;
55+
const row = Math.floor(i / n);
56+
if (i > 0) {
57+
chart.pan({x: -150});
58+
chart.update();
59+
}
60+
ctx.drawImage(chart.canvas, col * side, row * side, side, side);
61+
}
62+
Chart.helpers.clearCanvas(chart.canvas);
63+
chart.ctx.drawImage(canvas, 0, 0);
64+
}
65+
}
66+
};
16.6 KB
Loading

0 commit comments

Comments
 (0)