UNPKG

18.6 kBJavaScriptView Raw
1import { __assign, __extends } from "tslib";
2import { Component, isEqual, jsx } from '@antv/f-engine';
3import { updateRange, updateFollow } from './zoomUtil';
4import { each, isNumberEqual, isArray } from '@antv/util';
5import { quadraticOut as easeing } from './easing';
6function lerp(min, max, fraction) {
7 return (max - min) * fraction + min;
8}
9function isNumberEqualRange(aRange, bRange) {
10 if (!bRange) return false;
11 for (var i = 0, len = aRange.length; i < len; i++) {
12 if (!isNumberEqual(aRange[i], bRange[i])) return false;
13 }
14 return true;
15}
16function isEqualRange(aRange, bRange) {
17 if (!bRange) return false;
18 if (isArray(aRange)) {
19 return isNumberEqualRange(aRange, bRange);
20 }
21 // object
22 for (var i in aRange) {
23 if (!isNumberEqualRange(aRange[i], bRange[i])) return false;
24 }
25 return true;
26}
27function cloneScale(scale, scaleConfig) {
28 // @ts-ignore
29 return new scale.constructor(__assign(__assign({}, scale.__cfg__), scaleConfig));
30}
31export default (function (View) {
32 return /** @class */function (_super) {
33 __extends(Zoom, _super);
34 function Zoom(props) {
35 var _this = this;
36 var defaultProps = {
37 onPanStart: function onPanStart() {},
38 onPinchStart: function onPinchStart() {},
39 onPan: function onPan() {},
40 onPinch: function onPinch() {},
41 onInit: function onInit() {},
42 onPanEnd: function onPanEnd() {},
43 onPinchEnd: function onPinchEnd() {},
44 minCount: 10
45 };
46 _this = _super.call(this, __assign(__assign({}, defaultProps), props)) || this;
47 _this.scale = {};
48 _this.originScale = {};
49 //swipe end x y
50 _this.swipeEnd = {
51 startX: 0,
52 startY: 0,
53 endX: 0,
54 endY: 0
55 };
56 _this.onPanStart = function () {
57 var scale = _this.scale;
58 var onPanStart = _this.props.onPanStart;
59 _this.onStart();
60 onPanStart === null || onPanStart === void 0 ? void 0 : onPanStart({
61 scale: scale
62 });
63 };
64 _this.onPan = function (ev) {
65 var onPan = _this.props.onPan;
66 var dims = _this.dims;
67 var range = {};
68 each(dims, function (dim) {
69 if (dim === 'x') {
70 range['x'] = _this._doXPan(ev);
71 return;
72 }
73 if (dim === 'y') {
74 range['y'] = _this._doYPan(ev);
75 return;
76 }
77 });
78 _this.renderRange(range);
79 onPan === null || onPan === void 0 ? void 0 : onPan(ev);
80 };
81 _this.onPanEnd = function () {
82 var scale = _this.scale;
83 var onPanEnd = _this.props.onPanEnd;
84 _this.onEnd();
85 onPanEnd === null || onPanEnd === void 0 ? void 0 : onPanEnd({
86 scale: scale
87 });
88 };
89 _this.onPinchStart = function () {
90 var onPinchStart = _this.props.onPinchStart;
91 _this.onStart();
92 onPinchStart === null || onPinchStart === void 0 ? void 0 : onPinchStart();
93 };
94 _this.onPinch = function (ev) {
95 var onPinch = _this.props.onPinch;
96 var dims = _this.dims;
97 var range = {};
98 each(dims, function (dim) {
99 if (dim === 'x') {
100 range['x'] = _this._doXPinch(ev);
101 return;
102 }
103 if (dim === 'y') {
104 range['y'] = _this._doYPinch(ev);
105 return;
106 }
107 });
108 _this.renderRange(range);
109 onPinch === null || onPinch === void 0 ? void 0 : onPinch(ev);
110 };
111 _this.onPinchEnd = function () {
112 var scale = _this.scale;
113 var onPinchEnd = _this.props.onPinchEnd;
114 _this.onEnd();
115 onPinchEnd === null || onPinchEnd === void 0 ? void 0 : onPinchEnd({
116 scale: scale
117 });
118 };
119 _this.onStart = function () {
120 var state = _this.state;
121 var range = state.range;
122 _this.startRange = range;
123 _this._cancelAnimationFrame();
124 };
125 _this.onSwipe = function (ev) {
126 var _a = _this,
127 props = _a.props,
128 state = _a.state;
129 // 滑动速率
130 var velocity = ev.velocity,
131 direction = ev.direction,
132 _b = ev.velocityX,
133 velocityX = _b === void 0 ? 0 : _b,
134 _c = ev.velocityY,
135 velocityY = _c === void 0 ? 0 : _c,
136 points = ev.points;
137 var mode = props.mode,
138 swipe = props.swipe;
139 var range = state.range;
140 if (!swipe || !mode) {
141 return;
142 }
143 if (mode.length === 1) {
144 _this.animateSwipe(mode, range[mode], direction === 'right' || direction === 'down' ? -velocity : velocity);
145 return;
146 }
147 var _d = points[0],
148 x = _d.x,
149 y = _d.y;
150 // 边界处理
151 if (Math.abs((range === null || range === void 0 ? void 0 : range.x[0]) - 0) < 0.0005 && velocityX > 0) return;
152 if (Math.abs((range === null || range === void 0 ? void 0 : range.x[1]) - 1) < 0.0005 && velocityX < 0) return;
153 if (Math.abs((range === null || range === void 0 ? void 0 : range.y[0]) - 0) < 0.0005 && velocityY < 0) return;
154 if (Math.abs((range === null || range === void 0 ? void 0 : range.x[1]) - 1) < 0.0005 && velocityY > 0) return;
155 _this.swipeEnd = {
156 startX: x,
157 startY: y,
158 endX: x + velocityX * 50,
159 endY: y - velocityY * 50
160 };
161 _this.onStart();
162 _this.update();
163 };
164 _this.onEnd = function () {
165 _this.startRange = null;
166 };
167 var mode = props.mode;
168 _this.dims = isArray(mode) ? mode : [mode];
169 return _this;
170 }
171 Zoom.prototype.didMount = function () {
172 var scale = this.scale;
173 var onInit = this.props.onInit;
174 onInit({
175 scale: scale
176 });
177 this._bindEvents();
178 };
179 Zoom.prototype.willReceiveProps = function (nextProps) {
180 // @ts-ignore
181 var nextRange = nextProps.range,
182 nextData = nextProps.data;
183 var _a = this.props,
184 lastRange = _a.range,
185 lastData = _a.data;
186 if (nextData !== lastData) {
187 this._cancelAnimationFrame();
188 }
189 if (!isEqual(nextRange, lastRange)) {
190 var cacheRange_1 = {};
191 each(this.dims, function (dim) {
192 cacheRange_1[dim] = nextRange;
193 });
194 this.state = {
195 range: cacheRange_1
196 };
197 }
198 };
199 Zoom.prototype.willMount = function () {
200 var _this = this;
201 var _a = this,
202 props = _a.props,
203 dims = _a.dims;
204 var minCount = props.minCount,
205 range = props.range;
206 var valueLength = Number.MIN_VALUE;
207 var cacheRange = {};
208 each(dims, function (dim) {
209 var scale = _this._getScale(dim);
210 var values = scale.values;
211 valueLength = values.length > valueLength ? values.length : valueLength;
212 _this.scale[dim] = scale;
213 _this.originScale[dim] = cloneScale(scale);
214 _this.updateRange(range, dim);
215 cacheRange[dim] = range;
216 });
217 // 图表上最少显示 MIN_COUNT 个数据
218 this.minScale = minCount / valueLength;
219 this.renderRange(cacheRange);
220 };
221 Zoom.prototype.willUpdate = function () {
222 var _this = this;
223 var _a = this,
224 props = _a.props,
225 state = _a.state,
226 dims = _a.dims;
227 var minCount = props.minCount,
228 range = props.range;
229 var valueLength = Number.MIN_VALUE;
230 var cacheRange = {};
231 each(dims, function (dim) {
232 var scale = _this._getScale(dim);
233 // scale 没有变化, 不处理
234 if (scale === _this.scale[dim]) {
235 return;
236 }
237 var values = scale.values;
238 valueLength = values.length > valueLength ? values.length : valueLength;
239 _this.scale[dim] = scale;
240 _this.originScale[dim] = cloneScale(scale);
241 // 让 range 触发更新
242 _this.state.range[dim] = [0, 1];
243 _this.updateRange(range, dim);
244 cacheRange[dim] = range;
245 });
246 // 有变化
247 if (Object.keys(cacheRange).length > 0) {
248 this.minScale = minCount / valueLength;
249 var newRange = __assign(__assign({}, state.range), cacheRange);
250 this.renderRange(newRange);
251 }
252 };
253 Zoom.prototype.didUnmount = function () {
254 this._cancelAnimationFrame();
255 this._unBindEvents();
256 };
257 Zoom.prototype._requestAnimationFrame = function (calllback) {
258 var context = this.context;
259 var requestAnimationFrame = context.canvas.requestAnimationFrame;
260 this.loop = requestAnimationFrame(calllback);
261 return this.loop;
262 };
263 Zoom.prototype._cancelAnimationFrame = function () {
264 var _a = this,
265 loop = _a.loop,
266 context = _a.context;
267 if (loop) {
268 context.canvas.cancelAnimationFrame(loop);
269 }
270 };
271 Zoom.prototype._bindEvents = function () {
272 var _a = this.props,
273 chart = _a.chart,
274 pan = _a.pan,
275 pinch = _a.pinch,
276 swipe = _a.swipe;
277 // 统一绑定事件
278 if (pan !== false) {
279 chart.on('panstart', this.onPanStart);
280 chart.on('pan', this.onPan);
281 chart.on('panend', this.onPanEnd);
282 }
283 if (pinch !== false) {
284 chart.on('pinch', this.onPinch);
285 chart.on('pinchstart', this.onPinchStart);
286 chart.on('pinchend', this.onPinchEnd);
287 }
288 if (swipe !== false) {
289 chart.on('swipe', this.onSwipe);
290 }
291 };
292 Zoom.prototype._unBindEvents = function () {
293 var _a = this.props,
294 chart = _a.chart,
295 pan = _a.pan,
296 pinch = _a.pinch,
297 swipe = _a.swipe;
298 // 统一绑定事件
299 if (pan !== false) {
300 chart.off('panstart', this.onPanStart);
301 chart.off('pan', this.onPan);
302 chart.off('panend', this.onPanEnd);
303 }
304 if (pinch !== false) {
305 chart.off('pinch', this.onPinch);
306 chart.off('pinchstart', this.onPinchStart);
307 chart.off('pinchend', this.onPinchEnd);
308 }
309 if (swipe !== false) {
310 chart.off('swipe', this.onSwipe);
311 }
312 };
313 Zoom.prototype.update = function () {
314 var _this = this;
315 var _a = this.swipeEnd,
316 startX = _a.startX,
317 startY = _a.startY,
318 endX = _a.endX,
319 endY = _a.endY;
320 var x = lerp(startX, endX, 0.05);
321 var y = lerp(startY, endY, 0.05);
322 this.swipeEnd = {
323 startX: x,
324 startY: y,
325 endX: endX,
326 endY: endY
327 };
328 var props = this.props;
329 var coord = props.coord;
330 var coordWidth = coord.width,
331 coordHeight = coord.height;
332 var range = {};
333 range['x'] = this._doPan((x - startX) / coordWidth, 'x');
334 range['y'] = this._doPan((y - startY) / coordHeight, 'y');
335 this.renderRange(range);
336 this.startRange = range;
337 this._requestAnimationFrame(function () {
338 return _this.update();
339 });
340 if (Math.abs(x - endX) < 0.0005 && Math.abs(y - endY) < 0.0005) {
341 this.onEnd();
342 this._cancelAnimationFrame();
343 }
344 };
345 Zoom.prototype.animateSwipe = function (dim, dimRange, velocity) {
346 var _this = this;
347 var props = this.props;
348 var _a = props.swipeDuration,
349 swipeDuration = _a === void 0 ? 1000 : _a;
350 var diff = (dimRange[1] - dimRange[0]) * velocity;
351 var startTime = Date.now();
352 var updateRange = function updateRange(t) {
353 var newDimRange = [dimRange[0] + diff * t, dimRange[1] + diff * t];
354 var newRange = _this.updateRange(newDimRange, dim);
355 _this.renderRange({
356 x: newRange
357 });
358 };
359 // 更新动画
360 var _update = function update() {
361 // 计算动画已经进行的时间
362 var currentTime = Date.now() - startTime;
363 // 如果动画已经结束,则清除定时器
364 if (currentTime >= swipeDuration) {
365 updateRange(1);
366 return;
367 }
368 // 计算缓动值
369 var progress = currentTime / swipeDuration;
370 var easedProgress = easeing(progress);
371 updateRange(easedProgress);
372 _this._requestAnimationFrame(function () {
373 _update();
374 });
375 };
376 _update();
377 };
378 Zoom.prototype._doXPan = function (ev) {
379 var direction = ev.direction,
380 deltaX = ev.deltaX;
381 if (this.props.mode.length === 1 && (direction === 'up' || direction === 'down')) {
382 return this.state.range['x'];
383 }
384 ev.preventDefault && ev.preventDefault();
385 var props = this.props;
386 var coord = props.coord,
387 _a = props.panSensitive,
388 panSensitive = _a === void 0 ? 1 : _a;
389 var coordWidth = coord.width;
390 var ratio = deltaX / coordWidth * panSensitive;
391 var newRange = this._doPan(ratio, 'x');
392 return newRange;
393 };
394 Zoom.prototype._doYPan = function (ev) {
395 var direction = ev.direction,
396 deltaY = ev.deltaY;
397 if (this.props.mode.length === 1 && (direction === 'left' || direction === 'right')) {
398 return this.state.range['y'];
399 }
400 ev.preventDefault && ev.preventDefault();
401 var props = this.props;
402 var coord = props.coord,
403 _a = props.panSensitive,
404 panSensitive = _a === void 0 ? 1 : _a;
405 var coordHeight = coord.height;
406 var ratio = -deltaY / coordHeight * panSensitive;
407 var newRange = this._doPan(ratio, 'y');
408 return newRange;
409 };
410 Zoom.prototype._doPan = function (ratio, dim) {
411 var startRange = this.startRange;
412 var _a = startRange[dim],
413 start = _a[0],
414 end = _a[1];
415 var rangeLen = end - start;
416 var rangeOffset = rangeLen * ratio;
417 var newStart = start - rangeOffset;
418 var newEnd = end - rangeOffset;
419 var newRange = this.updateRange([newStart, newEnd], dim);
420 return newRange;
421 };
422 Zoom.prototype._doXPinch = function (ev) {
423 ev.preventDefault && ev.preventDefault();
424 var zoom = ev.zoom,
425 center = ev.center;
426 var props = this.props;
427 var coord = props.coord;
428 var coordWidth = coord.width,
429 left = coord.left,
430 right = coord.right;
431 var leftLen = Math.abs(center.x - left);
432 var rightLen = Math.abs(right - center.x);
433 // 计算左右缩放的比例
434 var leftZoom = leftLen / coordWidth;
435 var rightZoom = rightLen / coordWidth;
436 var newRange = this._doPinch(leftZoom, rightZoom, zoom, 'x');
437 return newRange;
438 };
439 Zoom.prototype._doYPinch = function (ev) {
440 ev.preventDefault && ev.preventDefault();
441 var zoom = ev.zoom,
442 center = ev.center;
443 var props = this.props;
444 var coord = props.coord;
445 var coordHeight = coord.height,
446 top = coord.top,
447 bottom = coord.bottom;
448 var topLen = Math.abs(center.y - top);
449 var bottomLen = Math.abs(bottom - center.y);
450 // 计算左右缩放的比例
451 var topZoom = topLen / coordHeight;
452 var bottomZoom = bottomLen / coordHeight;
453 var newRange = this._doPinch(topZoom, bottomZoom, zoom, 'y');
454 return newRange;
455 };
456 Zoom.prototype._doPinch = function (startRatio, endRatio, zoom, dim) {
457 var _a = this,
458 startRange = _a.startRange,
459 minScale = _a.minScale,
460 props = _a.props;
461 var _b = props.pinchSensitive,
462 pinchSensitive = _b === void 0 ? 1 : _b;
463 var _c = startRange[dim],
464 start = _c[0],
465 end = _c[1];
466 var zoomOffset = zoom < 1 ? (1 / zoom - 1) * pinchSensitive : (1 - zoom) * pinchSensitive;
467 var rangeLen = end - start;
468 var rangeOffset = rangeLen * zoomOffset;
469 var startOffset = rangeOffset * startRatio;
470 var endOffset = rangeOffset * endRatio;
471 var newStart = Math.max(0, start - startOffset);
472 var newEnd = Math.min(1, end + endOffset);
473 var newRange = [newStart, newEnd];
474 // 如果已经到了最小比例,则不能再继续再放大
475 if (newEnd - newStart < minScale) {
476 return this.state.range[dim];
477 }
478 return this.updateRange(newRange, dim);
479 };
480 Zoom.prototype.updateRange = function (originalRange, dim) {
481 if (!originalRange) return;
482 var start = originalRange[0],
483 end = originalRange[1];
484 var rangeLength = end - start;
485 // 处理边界值
486 var newRange;
487 if (start < 0) {
488 newRange = [0, rangeLength];
489 } else if (end > 1) {
490 newRange = [1 - rangeLength, 1];
491 } else {
492 newRange = originalRange;
493 }
494 var _a = this,
495 props = _a.props,
496 scale = _a.scale,
497 originScale = _a.originScale,
498 state = _a.state;
499 var data = props.data,
500 autoFit = props.autoFit;
501 var range = state.range;
502 if (range && isEqualRange(newRange, range[dim])) return newRange;
503 // 更新主 scale
504 updateRange(scale[dim], originScale[dim], newRange);
505 if (autoFit) {
506 var followScale = this._getFollowScales(dim);
507 this.updateFollow(followScale, scale[dim], data);
508 }
509 return newRange;
510 };
511 Zoom.prototype.updateFollow = function (scales, mainScale, data) {
512 updateFollow(scales, mainScale, data);
513 };
514 Zoom.prototype._getScale = function (dim) {
515 var _a = this.props,
516 coord = _a.coord,
517 chart = _a.chart;
518 if (dim === 'x') {
519 return coord.transposed ? chart.getYScales()[0] : chart.getXScales()[0];
520 } else {
521 return coord.transposed ? chart.getXScales()[0] : chart.getYScales()[0];
522 }
523 };
524 Zoom.prototype._getFollowScales = function (dim) {
525 var _a = this.props,
526 coord = _a.coord,
527 chart = _a.chart;
528 if (dim === 'x') {
529 return coord.transposed ? chart.getXScales() : chart.getYScales();
530 }
531 if (dim === 'y') {
532 return coord.transposed ? chart.getYScales() : chart.getXScales();
533 }
534 };
535 Zoom.prototype.renderRange = function (range) {
536 var _a = this,
537 state = _a.state,
538 props = _a.props;
539 if (isEqualRange(range, state.range)) return;
540 var chart = props.chart;
541 // 手势变化不执行动画
542 var animate = chart.animate;
543 chart.setAnimate(false);
544 // 后面的 forceUpdate 会强制更新,所以不用 setState,直接更新
545 state.range = range;
546 chart.forceUpdate(function () {
547 chart.setAnimate(animate);
548 });
549 };
550 Zoom.prototype.render = function () {
551 return jsx(View, __assign({}, this.props, this.state));
552 };
553 return Zoom;
554 }(Component);
555});
\No newline at end of file