UNPKG

@antv/f2

Version:

Charts for mobile visualization.

514 lines 16.9 kB
import { __assign, __extends } from "tslib"; import { Component, isEqual, jsx } from '@antv/f-engine'; import { updateRange, updateFollow } from './zoomUtil'; import { each, isNumberEqual, isArray } from '@antv/util'; import { quadraticOut as easeing } from './easing'; function lerp(min, max, fraction) { return (max - min) * fraction + min; } function isNumberEqualRange(aRange, bRange) { if (!bRange) return false; for (var i = 0, len = aRange.length; i < len; i++) { if (!isNumberEqual(aRange[i], bRange[i])) return false; } return true; } function isEqualRange(aRange, bRange) { if (!bRange) return false; if (isArray(aRange)) { return isNumberEqualRange(aRange, bRange); } // object for (var i in aRange) { if (!isNumberEqualRange(aRange[i], bRange[i])) return false; } return true; } function cloneScale(scale, scaleConfig) { // @ts-ignore return new scale.constructor(__assign(__assign({}, scale.__cfg__), scaleConfig)); } export default (function (View) { return /** @class */function (_super) { __extends(Zoom, _super); function Zoom(props) { var _this = this; var defaultProps = { onPanStart: function onPanStart() {}, onPinchStart: function onPinchStart() {}, onPan: function onPan() {}, onPinch: function onPinch() {}, onInit: function onInit() {}, onPanEnd: function onPanEnd() {}, onPinchEnd: function onPinchEnd() {}, minCount: 10 }; _this = _super.call(this, __assign(__assign({}, defaultProps), props)) || this; _this.scale = {}; _this.originScale = {}; //swipe end x y _this.swipeEnd = { startX: 0, startY: 0, endX: 0, endY: 0 }; _this.onStart = function () { var state = _this.state; var range = state.range; _this.startRange = range; _this.loop && cancelAnimationFrame(_this.loop); }; _this.onPan = function (ev) { var dims = _this.dims; var range = {}; each(dims, function (dim) { if (dim === 'x') { range['x'] = _this._doXPan(ev); return; } if (dim === 'y') { range['y'] = _this._doYPan(ev); return; } }); _this.renderRange(range); }; _this.onSwipe = function (ev) { var _a = _this, props = _a.props, state = _a.state; // 滑动速率 var velocity = ev.velocity, direction = ev.direction, _b = ev.velocityX, velocityX = _b === void 0 ? 0 : _b, _c = ev.velocityY, velocityY = _c === void 0 ? 0 : _c, points = ev.points; var mode = props.mode, swipe = props.swipe; var range = state.range; if (!swipe || !mode) { return; } if (mode.length === 1) { _this.animateSwipe(mode, range[mode], direction === 'right' || direction === 'down' ? -velocity : velocity); return; } var _d = points[0], x = _d.x, y = _d.y; // 边界处理 if (Math.abs((range === null || range === void 0 ? void 0 : range.x[0]) - 0) < 0.0005 && velocityX > 0) return; if (Math.abs((range === null || range === void 0 ? void 0 : range.x[1]) - 1) < 0.0005 && velocityX < 0) return; if (Math.abs((range === null || range === void 0 ? void 0 : range.y[0]) - 0) < 0.0005 && velocityY < 0) return; if (Math.abs((range === null || range === void 0 ? void 0 : range.x[1]) - 1) < 0.0005 && velocityY > 0) return; _this.swipeEnd = { startX: x, startY: y, endX: x + velocityX * 50, endY: y - velocityY * 50 }; _this.onStart(); _this.update(); }; _this.onPinch = function (ev) { var dims = _this.dims; var range = {}; each(dims, function (dim) { if (dim === 'x') { range['x'] = _this._doXPinch(ev); return; } if (dim === 'y') { range['y'] = _this._doYPinch(ev); return; } }); _this.renderRange(range); }; _this.onEnd = function () { _this.startRange = null; }; var mode = props.mode; _this.dims = isArray(mode) ? mode : [mode]; return _this; } Zoom.prototype.didMount = function () { var scale = this.scale; var onInit = this.props.onInit; onInit({ scale: scale }); this._bindEvents(); }; Zoom.prototype.willReceiveProps = function (nextProps) { var nextRange = nextProps.range; var lastRange = this.props.range; if (!isEqual(nextRange, lastRange)) { var cacheRange_1 = {}; each(this.dims, function (dim) { cacheRange_1[dim] = nextRange; }); this.state = { range: cacheRange_1 }; } }; Zoom.prototype.willMount = function () { var _this = this; var _a = this, props = _a.props, dims = _a.dims; var minCount = props.minCount, range = props.range; var valueLength = Number.MIN_VALUE; var cacheRange = {}; each(dims, function (dim) { var scale = _this._getScale(dim); var values = scale.values; valueLength = values.length > valueLength ? values.length : valueLength; _this.scale[dim] = scale; _this.originScale[dim] = cloneScale(scale); _this.updateRange(range, dim); cacheRange[dim] = range; }); // 图表上最少显示 MIN_COUNT 个数据 this.minScale = minCount / valueLength; this.renderRange(cacheRange); }; Zoom.prototype.willUpdate = function () { var _this = this; var _a = this, props = _a.props, state = _a.state, dims = _a.dims; var minCount = props.minCount, range = props.range; var valueLength = Number.MIN_VALUE; var cacheRange = {}; each(dims, function (dim) { var scale = _this._getScale(dim); // scale 没有变化, 不处理 if (scale === _this.scale[dim]) { return; } var values = scale.values; valueLength = values.length > valueLength ? values.length : valueLength; _this.scale[dim] = scale; _this.originScale[dim] = cloneScale(scale); // 让 range 触发更新 _this.state.range[dim] = [0, 1]; _this.updateRange(range, dim); cacheRange[dim] = range; }); // 有变化 if (Object.keys(cacheRange).length > 0) { this.minScale = minCount / valueLength; var newRange = __assign(__assign({}, state.range), cacheRange); this.renderRange(newRange); } }; Zoom.prototype.didUnmount = function () { this.loop && cancelAnimationFrame(this.loop); }; Zoom.prototype._bindEvents = function () { var _this = this; var scale = this.scale; var _a = this.props, chart = _a.chart, onPinchStart = _a.onPinchStart, onPanStart = _a.onPanStart, onPanEnd = _a.onPanEnd, pan = _a.pan, pinch = _a.pinch, swipe = _a.swipe, onPan = _a.onPan, onPinch = _a.onPinch, onPinchEnd = _a.onPinchEnd; // 统一绑定事件 if (pan !== false) { chart.on('panstart', function () { _this.onStart(); onPanStart({ scale: scale }); }); chart.on('pan', function (ev) { _this.onPan(ev); onPan(ev); }); chart.on('panend', function () { _this.onEnd(); onPanEnd({ scale: scale }); }); } if (pinch !== false) { chart.on('pinchstart', function () { _this.onStart(); onPinchStart(); }); chart.on('pinch', function (ev) { _this.onPinch(ev); onPinch(ev); }); chart.on('pinchend', function () { _this.onEnd(); onPinchEnd({ scale: scale }); }); } if (swipe !== false) { chart.on('swipe', this.onSwipe); } }; Zoom.prototype.update = function () { var _this = this; var _a = this.swipeEnd, startX = _a.startX, startY = _a.startY, endX = _a.endX, endY = _a.endY; var x = lerp(startX, endX, 0.05); var y = lerp(startY, endY, 0.05); this.swipeEnd = { startX: x, startY: y, endX: endX, endY: endY }; var props = this.props; var coord = props.coord; var coordWidth = coord.width, coordHeight = coord.height; var range = {}; range['x'] = this._doPan((x - startX) / coordWidth, 'x'); range['y'] = this._doPan((y - startY) / coordHeight, 'y'); this.renderRange(range); this.startRange = range; this.loop = requestAnimationFrame(function () { return _this.update(); }); if (Math.abs(x - endX) < 0.0005 && Math.abs(y - endY) < 0.0005) { this.onEnd(); cancelAnimationFrame(this.loop); } }; Zoom.prototype.animateSwipe = function (dim, dimRange, velocity) { var _this = this; var _a = this, context = _a.context, props = _a.props; var requestAnimationFrame = context.canvas.requestAnimationFrame; var _b = props.swipeDuration, swipeDuration = _b === void 0 ? 1000 : _b; var diff = (dimRange[1] - dimRange[0]) * velocity; var startTime = Date.now(); var updateRange = function updateRange(t) { var newDimRange = [dimRange[0] + diff * t, dimRange[1] + diff * t]; var newRange = _this.updateRange(newDimRange, dim); _this.renderRange({ x: newRange }); }; // 更新动画 var update = function update() { // 计算动画已经进行的时间 var currentTime = Date.now() - startTime; // 如果动画已经结束,则清除定时器 if (currentTime >= swipeDuration) { updateRange(1); return; } // 计算缓动值 var progress = currentTime / swipeDuration; var easedProgress = easeing(progress); updateRange(easedProgress); requestAnimationFrame(function () { update(); }); }; update(); }; Zoom.prototype._doXPan = function (ev) { var direction = ev.direction, deltaX = ev.deltaX; if (this.props.mode.length === 1 && (direction === 'up' || direction === 'down')) { return this.state.range['x']; } ev.preventDefault && ev.preventDefault(); var props = this.props; var coord = props.coord, _a = props.panSensitive, panSensitive = _a === void 0 ? 1 : _a; var coordWidth = coord.width; var ratio = deltaX / coordWidth * panSensitive; var newRange = this._doPan(ratio, 'x'); return newRange; }; Zoom.prototype._doYPan = function (ev) { var direction = ev.direction, deltaY = ev.deltaY; if (this.props.mode.length === 1 && (direction === 'left' || direction === 'right')) { return this.state.range['y']; } ev.preventDefault && ev.preventDefault(); var props = this.props; var coord = props.coord, _a = props.panSensitive, panSensitive = _a === void 0 ? 1 : _a; var coordHeight = coord.height; var ratio = -deltaY / coordHeight * panSensitive; var newRange = this._doPan(ratio, 'y'); return newRange; }; Zoom.prototype._doPan = function (ratio, dim) { var startRange = this.startRange; var _a = startRange[dim], start = _a[0], end = _a[1]; var rangeLen = end - start; var rangeOffset = rangeLen * ratio; var newStart = start - rangeOffset; var newEnd = end - rangeOffset; var newRange = this.updateRange([newStart, newEnd], dim); return newRange; }; Zoom.prototype._doXPinch = function (ev) { ev.preventDefault && ev.preventDefault(); var zoom = ev.zoom, center = ev.center; var props = this.props; var coord = props.coord; var coordWidth = coord.width, left = coord.left, right = coord.right; var leftLen = Math.abs(center.x - left); var rightLen = Math.abs(right - center.x); // 计算左右缩放的比例 var leftZoom = leftLen / coordWidth; var rightZoom = rightLen / coordWidth; var newRange = this._doPinch(leftZoom, rightZoom, zoom, 'x'); return newRange; }; Zoom.prototype._doYPinch = function (ev) { ev.preventDefault && ev.preventDefault(); var zoom = ev.zoom, center = ev.center; var props = this.props; var coord = props.coord; var coordHeight = coord.height, top = coord.top, bottom = coord.bottom; var topLen = Math.abs(center.y - top); var bottomLen = Math.abs(bottom - center.y); // 计算左右缩放的比例 var topZoom = topLen / coordHeight; var bottomZoom = bottomLen / coordHeight; var newRange = this._doPinch(topZoom, bottomZoom, zoom, 'y'); return newRange; }; Zoom.prototype._doPinch = function (startRatio, endRatio, zoom, dim) { var _a = this, startRange = _a.startRange, minScale = _a.minScale, props = _a.props; var _b = props.pinchSensitive, pinchSensitive = _b === void 0 ? 1 : _b; var _c = startRange[dim], start = _c[0], end = _c[1]; var zoomOffset = zoom < 1 ? (1 / zoom - 1) * pinchSensitive : (1 - zoom) * pinchSensitive; var rangeLen = end - start; var rangeOffset = rangeLen * zoomOffset; var startOffset = rangeOffset * startRatio; var endOffset = rangeOffset * endRatio; var newStart = Math.max(0, start - startOffset); var newEnd = Math.min(1, end + endOffset); var newRange = [newStart, newEnd]; // 如果已经到了最小比例,则不能再继续再放大 if (newEnd - newStart < minScale) { return this.state.range[dim]; } return this.updateRange(newRange, dim); }; Zoom.prototype.updateRange = function (originalRange, dim) { if (!originalRange) return; var start = originalRange[0], end = originalRange[1]; var rangeLength = end - start; // 处理边界值 var newRange; if (start < 0) { newRange = [0, rangeLength]; } else if (end > 1) { newRange = [1 - rangeLength, 1]; } else { newRange = originalRange; } var _a = this, props = _a.props, scale = _a.scale, originScale = _a.originScale, state = _a.state; var data = props.data, autoFit = props.autoFit; var range = state.range; if (range && isEqualRange(newRange, range[dim])) return newRange; // 更新主 scale updateRange(scale[dim], originScale[dim], newRange); if (autoFit) { var followScale = this._getFollowScales(dim); this.updateFollow(followScale, scale[dim], data); } return newRange; }; Zoom.prototype.updateFollow = function (scales, mainScale, data) { updateFollow(scales, mainScale, data); }; Zoom.prototype._getScale = function (dim) { var _a = this.props, coord = _a.coord, chart = _a.chart; if (dim === 'x') { return coord.transposed ? chart.getYScales()[0] : chart.getXScales()[0]; } else { return coord.transposed ? chart.getXScales()[0] : chart.getYScales()[0]; } }; Zoom.prototype._getFollowScales = function (dim) { var _a = this.props, coord = _a.coord, chart = _a.chart; if (dim === 'x') { return coord.transposed ? chart.getXScales() : chart.getYScales(); } if (dim === 'y') { return coord.transposed ? chart.getYScales() : chart.getXScales(); } }; Zoom.prototype.renderRange = function (range) { var _a = this, state = _a.state, props = _a.props; if (isEqualRange(range, state.range)) return; var chart = props.chart; // 手势变化不执行动画 var animate = chart.animate; chart.setAnimate(false); // 后面的 forceUpdate 会强制更新,所以不用 setState,直接更新 state.range = range; chart.forceUpdate(function () { chart.setAnimate(animate); }); }; Zoom.prototype.render = function () { return jsx(View, __assign({}, this.props, this.state)); }; return Zoom; }(Component); });