@antv/f2
Version:
Charts for mobile visualization.
514 lines • 16.9 kB
JavaScript
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);
});