• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

chartjs / chartjs-plugin-zoom / 11878789386

17 Nov 2024 12:21PM UTC coverage: 83.27% (-0.8%) from 84.067%
11878789386

push

github

web-flow
fix: enabling pan after chart initialization (#863)

213 of 270 branches covered (78.89%)

Branch coverage included in aggregate %.

8 of 13 new or added lines in 2 files covered. (61.54%)

444 of 519 relevant lines covered (85.55%)

1453.14 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

52.99
/src/hammer.js
1
import {callback as call} from 'chart.js/helpers';
2
import Hammer from 'hammerjs';
3
import {pan, zoom} from './core';
4
import {getState} from './state';
5
import {directionEnabled, getEnabledScalesByPoint, getModifierKey, keyNotPressed, keyPressed} from './utils';
6

7
function createEnabler(chart, state) {
8
  return function(recognizer, event) {
92✔
9
    const {pan: panOptions, zoom: zoomOptions = {}} = state.options;
472!
10
    if (!panOptions || !panOptions.enabled) {
472!
11
      return false;
×
12
    }
13
    const srcEvent = event && event.srcEvent;
472✔
14
    if (!srcEvent) { // Sometimes Hammer queries this with a null event.
472✔
15
      return true;
92✔
16
    }
17
    if (!state.panning && event.pointerType === 'mouse' && (
380!
18
      keyNotPressed(getModifierKey(panOptions), srcEvent) || keyPressed(getModifierKey(zoomOptions.drag), srcEvent))
19
    ) {
20
      call(panOptions.onPanRejected, [{chart, event}]);
×
21
      return false;
×
22
    }
23
    return true;
380✔
24
  };
25
}
26

27
function pinchAxes(p0, p1) {
28
  // fingers position difference
29
  const pinchX = Math.abs(p0.clientX - p1.clientX);
×
30
  const pinchY = Math.abs(p0.clientY - p1.clientY);
×
31

32
  // diagonal fingers will change both (xy) axes
33
  const p = pinchX / pinchY;
×
34
  let x, y;
35
  if (p > 0.3 && p < 1.7) {
×
36
    x = y = true;
×
37
  } else if (pinchX > pinchY) {
×
38
    x = true;
×
39
  } else {
40
    y = true;
×
41
  }
42
  return {x, y};
×
43
}
44

45
function handlePinch(chart, state, e) {
46
  if (state.scale) {
×
47
    const {center, pointers} = e;
×
48
    // Hammer reports the total scaling. We need the incremental amount
49
    const zoomPercent = 1 / state.scale * e.scale;
×
50
    const rect = e.target.getBoundingClientRect();
×
51
    const pinch = pinchAxes(pointers[0], pointers[1]);
×
52
    const mode = state.options.zoom.mode;
×
53
    const amount = {
×
54
      x: pinch.x && directionEnabled(mode, 'x', chart) ? zoomPercent : 1,
×
55
      y: pinch.y && directionEnabled(mode, 'y', chart) ? zoomPercent : 1,
×
56
      focalPoint: {
57
        x: center.x - rect.left,
58
        y: center.y - rect.top
59
      }
60
    };
61

62
    zoom(chart, amount);
×
63

64
    // Keep track of overall scale
65
    state.scale = e.scale;
×
66
  }
67
}
68

69
function startPinch(chart, state) {
70
  if (state.options.zoom.pinch.enabled) {
×
71
    call(state.options.zoom.onZoomStart, [{chart}]);
×
72
    state.scale = 1;
×
73
  }
74
}
75

76
function endPinch(chart, state, e) {
77
  if (state.scale) {
×
78
    handlePinch(chart, state, e);
×
79
    state.scale = null; // reset
×
80
    call(state.options.zoom.onZoomComplete, [{chart}]);
×
81
  }
82
}
83

84
function handlePan(chart, state, e) {
85
  const delta = state.delta;
224✔
86
  if (delta) {
224✔
87
    state.panning = true;
216✔
88
    pan(chart, {x: e.deltaX - delta.x, y: e.deltaY - delta.y}, state.panScales);
216✔
89
    state.delta = {x: e.deltaX, y: e.deltaY};
216✔
90
  }
91
}
92

93
function startPan(chart, state, event) {
94
  const {enabled, onPanStart, onPanRejected} = state.options.pan;
76✔
95
  if (!enabled) {
76!
96
    return;
×
97
  }
98
  const rect = event.target.getBoundingClientRect();
76✔
99
  const point = {
76✔
100
    x: event.center.x - rect.left,
101
    y: event.center.y - rect.top
102
  };
103

104
  if (call(onPanStart, [{chart, event, point}]) === false) {
76✔
105
    return call(onPanRejected, [{chart, event}]);
4✔
106
  }
107

108
  state.panScales = getEnabledScalesByPoint(state.options.pan, point, chart);
72✔
109
  state.delta = {x: 0, y: 0};
72✔
110
  clearTimeout(state.panEndTimeout);
72✔
111
  handlePan(chart, state, event);
72✔
112
}
113

114
function endPan(chart, state) {
115
  state.delta = null;
76✔
116
  if (state.panning) {
76✔
117
    state.panEndTimeout = setTimeout(() => (state.panning = false), 500);
72✔
118
    call(state.options.pan.onPanComplete, [{chart}]);
72✔
119
  }
120
}
121

122
const hammers = new WeakMap();
4✔
123
export function startHammer(chart, options) {
124
  const state = getState(chart);
296✔
125
  const canvas = chart.canvas;
296✔
126
  const {pan: panOptions, zoom: zoomOptions} = options;
296✔
127

128
  const mc = new Hammer.Manager(canvas);
296✔
129
  if (zoomOptions && zoomOptions.pinch.enabled) {
296!
130
    mc.add(new Hammer.Pinch());
×
131
    mc.on('pinchstart', () => startPinch(chart, state));
×
132
    mc.on('pinch', (e) => handlePinch(chart, state, e));
×
133
    mc.on('pinchend', (e) => endPinch(chart, state, e));
×
134
  }
135

136
  if (panOptions && panOptions.enabled) {
296✔
137
    mc.add(new Hammer.Pan({
92✔
138
      threshold: panOptions.threshold,
139
      enable: createEnabler(chart, state)
140
    }));
141
    mc.on('panstart', (e) => startPan(chart, state, e));
92✔
142
    mc.on('panmove', (e) => handlePan(chart, state, e));
152✔
143
    mc.on('panend', () => endPan(chart, state));
92✔
144
  }
145

146
  hammers.set(chart, mc);
296✔
147
}
148

149
export function stopHammer(chart) {
150
  const mc = hammers.get(chart);
10,011✔
151
  if (mc) {
10,011✔
152
    mc.remove('pinchstart');
296✔
153
    mc.remove('pinch');
296✔
154
    mc.remove('pinchend');
296✔
155
    mc.remove('panstart');
296✔
156
    mc.remove('pan');
296✔
157
    mc.remove('panend');
296✔
158
    mc.destroy();
296✔
159
    hammers.delete(chart);
296✔
160
  }
161
}
162

163
export function hammerOptionsChanged(oldOptions, newOptions) {
164
  const {pan: oldPan, zoom: oldZoom} = oldOptions;
1,516✔
165
  const {pan: newPan, zoom: newZoom} = newOptions;
1,516✔
166

167
  if (oldZoom?.zoom?.pinch?.enabled !== newZoom?.zoom?.pinch?.enabled) {
1,516!
NEW
168
    return true;
×
169
  }
170
  if (oldPan?.enabled !== newPan?.enabled) {
1,516!
NEW
171
    return true;
×
172
  }
173
  if (oldPan?.threshold !== newPan?.threshold) {
1,516!
NEW
174
    return true;
×
175
  }
176

177
  return false;
1,516✔
178
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc