UNPKG

@antv/f2

Version:

Charts for mobile visualization.

452 lines 15.5 kB
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); });