@antv/f2
Version:
Charts for mobile visualization.
452 lines • 15.5 kB
JavaScript
import { __assign, __extends } from "tslib";
import { jsx, isEqual, Component } from '@antv/f-engine';
import { deepMix, isFunction, mix, each, clone, isString, isNumber, isArray, isNil } from '@antv/util';
var SHOW_MIDDLE_LABEL_THRESHOLD = 10;
export default (function (View) {
return /** @class */function (_super) {
__extends(Axis, _super);
function Axis(props) {
var _this = _super.call(this, props) || this;
_this.axisStyle = {};
_this.maxLabelWidth = 0;
var chart = props.chart,
field = props.field;
var scaleOption = _this.getScaleOption(props);
chart.setScale(field, scaleOption);
return _this;
}
Axis.prototype.willReceiveProps = function (nextProps) {
var lastProps = this.props;
var chart = nextProps.chart,
field = nextProps.field;
var nextScaleOption = this.getScaleOption(nextProps);
var lastScaleOption = this.getScaleOption(lastProps);
if (!isEqual(nextScaleOption, lastScaleOption)) {
chart.setScale(field, nextScaleOption);
}
};
Axis.prototype.willMount = function () {
this.updateCoord();
};
Axis.prototype.willUpdate = function () {
this.updateCoord();
};
Axis.prototype.getScaleOption = function (props) {
var type = props.type,
tickCount = props.tickCount,
range = props.range,
mask = props.mask,
formatter = props.formatter,
ticks = props.ticks,
min = props.min,
max = props.max,
nice = props.nice;
return {
type: type,
tickCount: tickCount,
range: range,
mask: mask,
formatter: formatter,
min: min,
max: max,
nice: nice,
ticks: ticks
};
};
Axis.prototype._getDimType = function () {
var props = this.props;
var field = props.field,
chart = props.chart;
var xScales = chart.getXScales();
var scales = xScales.filter(function (scale) {
return scale.field === field;
});
return scales.length > 0 ? 'x' : 'y';
};
// 获取ticks最大的宽高
Axis.prototype.getMaxBBox = function (ticks, style) {
var context = this.context;
var measureText = context.measureText;
var label = style.label,
labelOffset = style.labelOffset;
var width = 0;
var height = 0;
ticks.forEach(function (tick) {
if (!label) return;
var _a = tick.labelStyle,
labelStyle = _a === void 0 ? {} : _a,
text = tick.text;
var bbox = measureText(labelStyle.text || text, __assign(__assign({}, label), labelStyle));
width = Math.max(width, bbox.width);
height = Math.max(height, bbox.height);
});
if (!width && !height) {
return {
width: width,
height: height
};
}
var bbox = {
width: width + labelOffset,
height: height + labelOffset
};
return bbox;
};
Axis.prototype._getPosition = function () {
var props = this.props;
var position = props.position,
coord = props.coord;
if (position) {
return position;
}
var dimType = this._getDimType();
if (coord.transposed) {
return dimType === 'x' ? 'left' : 'bottom';
}
return dimType === 'x' ? 'bottom' : 'left';
};
Axis.prototype.getTicks = function () {
var props = this.props;
var field = props.field,
chart = props.chart;
var scale = chart.getScale(field);
var ticks = scale.getTicks();
// 设置tick的样式
ticks = this._setTicksStyle(ticks);
ticks = this._generateGridPoints(ticks);
return ticks;
};
/**
* 生成极坐标下网格线的交叉点
* @param ticks
* @returns
*/
Axis.prototype._generateGridPoints = function (ticks) {
var props = this.props;
var chart = props.chart,
coord = props.coord;
if (!coord.isPolar) {
return ticks;
}
var dimType = this._getDimType();
// 只需要在 y 的时候生成
if (dimType !== 'y') {
return ticks;
}
var xScale = chart.getXScales()[0];
var xTicks = xScale.getTicks();
ticks.forEach(function (tick) {
var gridPoints = xTicks.map(function (xTick) {
return coord.convertPoint({
x: xTick.value,
y: tick.value
});
});
// 添加第 1 个点,形成环状
gridPoints.push(gridPoints[0]);
tick.gridPoints = gridPoints;
});
return ticks;
};
Axis.prototype._setTicksStyle = function (ticks) {
var _this = this;
var _a = this,
props = _a.props,
context = _a.context;
var theme = context.theme,
px2hd = context.px2hd;
var _b = props.style,
style = _b === void 0 ? {} : _b;
var themeAxis = theme.axis;
each(themeAxis, function (value, key) {
// 关闭tick的样式
if (style[key] === null) {
return;
}
var styleValue = isFunction(style[key]) ? undefined : style[key];
if (isString(value) || isNumber(value)) {
_this.axisStyle[key] = px2hd(styleValue) || value;
} else if (isArray(styleValue)) {
_this.axisStyle[key] = styleValue.map(function (d) {
return px2hd(deepMix(clone(value), d));
});
} else {
_this.axisStyle[key] = px2hd(deepMix(clone(value), styleValue));
}
});
return ticks.map(function (tick, index) {
var label = style.label,
grid = style.grid;
var defaultLabelStyle = themeAxis.label,
defaultGridStyle = themeAxis.grid;
if (isFunction(label)) {
tick.labelStyle = px2hd(mix({}, defaultLabelStyle, label(tick.text, index, ticks)));
}
if (isFunction(grid)) {
tick.gridStyle = px2hd(mix({}, defaultGridStyle, grid(tick.text, index, ticks.length)));
}
return tick;
});
};
Axis.prototype.convertTicks = function (ticks) {
var props = this.props;
var coord = props.coord;
var dimType = this._getDimType();
var otherDim = dimType === 'x' ? 'y' : 'x';
return ticks.map(function (tick) {
var _a, _b;
var start = coord.convertPoint((_a = {}, _a[dimType] = tick.value, _a[otherDim] = 0, _a));
var end = coord.convertPoint((_b = {}, _b[dimType] = tick.value, _b[otherDim] = 1, _b));
return __assign(__assign({}, tick), {
points: [start, end]
});
});
};
Axis.prototype.calculateLabelOverflow = function (lastTick) {
if (!lastTick) {
return 0;
}
var _a = this,
props = _a.props,
context = _a.context,
axisStyle = _a.axisStyle;
var measureText = context.measureText;
var label = axisStyle.label;
var coord = props.coord;
var _b = lastTick.labelStyle,
labelStyle = _b === void 0 ? {} : _b,
text = lastTick.text;
var tickBBox = measureText(labelStyle.text || text, __assign(__assign({}, label), labelStyle));
var lastTickPoint = coord.convertPoint({
x: lastTick.value,
y: 0
});
var labelRightEdge = lastTickPoint.x;
var _c = __assign(__assign({}, label), labelStyle).align,
align = _c === void 0 ? 'center' : _c;
if (align === 'center') {
labelRightEdge += tickBBox.width / 2;
} else if (align === 'left' || align === 'start') {
labelRightEdge += tickBBox.width;
}
return labelRightEdge > coord.right ? labelRightEdge - coord.right : 0;
};
Axis.prototype._getXTicksDistance = function (ticks) {
var props = this.props;
var coord = props.coord;
var firstPoint = coord.convertPoint({
x: ticks[0].value,
y: 0
});
var secondPoint = coord.convertPoint({
x: ticks[1].value,
y: 0
});
return Math.abs(secondPoint.x - firstPoint.x);
};
Axis.prototype.measureLayout = function (ticks) {
var _a = this,
props = _a.props,
context = _a.context;
var visible = props.visible,
coord = props.coord,
style = props.style,
_b = props.labelAutoRotate,
labelAutoRotate = _b === void 0 ? false : _b,
_c = props.labelAutoHide,
labelAutoHide = _c === void 0 ? false : _c;
if (visible === false) {
return null;
}
var _d = style || {},
customWidth = _d.width,
customHeight = _d.height;
var bbox = this.getMaxBBox(ticks, this.axisStyle);
var isPolar = coord.isPolar;
var dimType = this._getDimType();
var width = isNil(customWidth) ? bbox.width : context.px2hd(customWidth);
var height = isNil(customHeight) ? bbox.height : context.px2hd(customHeight);
if (isPolar) {
// 机坐标系的 y 不占位置
if (dimType === 'y') {
return null;
}
// 4 个方向都需要留空
return ['top', 'right', 'bottom', 'left'].map(function (position) {
return {
position: position,
width: width,
height: height
};
});
}
// 直角坐标系下
var position = this._getPosition();
if ((labelAutoRotate || labelAutoHide) && dimType === 'x') {
var lastTick = ticks[ticks.length - 1];
var overflowWidth = this.calculateLabelOverflow(lastTick);
return [{
position: position,
width: width,
height: height
}, {
position: 'right',
width: overflowWidth,
height: 0
}];
}
return {
position: position,
width: width,
height: height
};
};
Axis.prototype.findSuitableRotation = function (ticks) {
var context = this.context;
var measureText = context.measureText;
var averageSpace = this._getXTicksDistance([ticks[0], ticks[1]]);
var label = this.axisStyle.label;
var _a = ticks[0],
_b = _a.labelStyle,
labelStyle = _b === void 0 ? {} : _b,
text = _a.text;
var bbox = measureText(labelStyle.text || text, __assign(__assign({}, label), labelStyle));
var labelHeight = bbox.height;
// 安全距离
var safetyDistance = 2;
var availableSpace = labelHeight + safetyDistance;
var sinValue = availableSpace / averageSpace;
var clampedSinValue = Math.max(-1, Math.min(1, sinValue));
var theoreticalAngle = Math.asin(clampedSinValue) * 180 / Math.PI;
var ceiledAngle = Math.ceil(theoreticalAngle);
if (ceiledAngle > 0 && ceiledAngle <= 90) {
ticks.forEach(function (tick) {
tick.labelStyle = tick.labelStyle || {};
tick.labelStyle.align = 'start';
tick.labelStyle.transform = "rotate(".concat(ceiledAngle, "deg)");
tick.labelStyle.transformOrigin = '0 50%';
});
}
};
Axis.prototype.hasOverlapAtSeq = function (ticks, step) {
var px2hd = this.context.px2hd;
var _a = this.props.safetyDistance,
safetyDistance = _a === void 0 ? 2 : _a;
var XDistance = this._getXTicksDistance([ticks[0], ticks[step]]);
var prevIdx = 0;
for (var currIdx = step; currIdx <= ticks.length - 1; currIdx += step) {
var label = this.axisStyle.label;
var _b = ticks[prevIdx].labelStyle,
labelStyle = _b === void 0 ? {} : _b;
var _c = __assign(__assign({}, label), labelStyle).align,
align = _c === void 0 ? 'center' : _c;
var minDistance = (ticks[prevIdx].labelWidth + ticks[currIdx].labelWidth) / 2 + px2hd(safetyDistance);
if (prevIdx === 0 && align === 'between') {
minDistance += ticks[prevIdx].labelWidth / 2;
}
if (XDistance < minDistance) {
return true;
}
prevIdx = currIdx;
}
return false;
};
Axis.prototype.hasOverlap = function (ticks) {
var context = this.context;
var measureText = context.measureText;
var tickCount = ticks.length;
var label = this.axisStyle.label;
for (var i = 0; i < tickCount; i++) {
var tick = ticks[i];
var _a = tick.labelStyle,
labelStyle = _a === void 0 ? {} : _a,
text = tick.text;
var bbox = measureText(labelStyle.text || text, __assign(__assign({}, label), labelStyle));
tick.labelWidth = bbox.width;
this.maxLabelWidth = Math.max(this.maxLabelWidth, bbox.width);
}
return this.hasOverlapAtSeq(ticks, 1);
};
Axis.prototype.findLabelsToHide = function (ticks) {
var props = this.props;
var coord = props.coord;
var tickCount = ticks.length;
var initialSeq = Math.floor(this.maxLabelWidth / (coord.width / (tickCount - 1)));
var range = tickCount - 1;
var maxSeq = Math.floor(range / 2);
var finalSeq = initialSeq;
while (finalSeq <= maxSeq && range % finalSeq !== 0) {
finalSeq++;
}
while (finalSeq <= maxSeq && this.hasOverlapAtSeq(ticks, finalSeq)) {
finalSeq++;
while (finalSeq <= maxSeq && range % finalSeq !== 0) {
finalSeq++;
}
}
if (finalSeq === 1) {
return;
}
ticks.forEach(function (tick) {
tick.visible = false;
});
// 没找到最佳步长,则保留第一个和最后一个数据,如果总range较大,保留中间的label
if (finalSeq > maxSeq) {
ticks[0].visible = true;
if (range > SHOW_MIDDLE_LABEL_THRESHOLD && !this.hasOverlapAtSeq(ticks, maxSeq)) {
ticks[maxSeq].visible = true;
}
ticks[range].visible = true;
return;
}
for (var i = 0; i <= range; i += finalSeq) {
ticks[i].visible = true;
}
};
// 计算坐标轴布局并更新坐标系
Axis.prototype.updateCoord = function () {
var props = this.props;
var chart = props.chart,
_a = props.labelAutoRotate,
labelAutoRotate = _a === void 0 ? false : _a,
_b = props.labelAutoHide,
labelAutoHide = _b === void 0 ? false : _b;
var dimType = this._getDimType();
var ticks = this.getTicks();
if ((labelAutoRotate || labelAutoHide) && dimType === 'x' && this.hasOverlap(ticks)) {
if (labelAutoRotate) {
this.findSuitableRotation(ticks);
}
if (labelAutoHide) {
this.findLabelsToHide(ticks);
}
this.ticks = ticks;
}
// 测量并获取布局信息
var layout = this.measureLayout(ticks);
// 更新图表的坐标系
chart.updateCoordFor(this, layout);
};
Axis.prototype.render = function () {
var _a = this,
props = _a.props,
axisStyle = _a.axisStyle;
var visible = props.visible,
coord = props.coord;
var dimType = this._getDimType();
if (visible === false) {
return null;
}
var ticks = this.ticks ? this.ticks : this.getTicks();
var position = this._getPosition();
return jsx(View, __assign({}, props, {
style: axisStyle,
ticks: this.convertTicks(ticks),
coord: coord,
position: position,
dimType: dimType
}));
};
return Axis;
}(Component);
});