@antv/f2
Version:
Charts for mobile visualization.
677 lines • 21.7 kB
JavaScript
import { __assign, __extends, __rest } from "tslib";
import { isFunction, each, upperFirst, mix, groupToMap, isObject, flatten, isNull, find } from '@antv/util';
import Selection from './selection';
import { Dodge, Jitter, Stack, Symmetric } from '../../deps/f2-adjust/src';
import { toTimeStamp } from '../../util/index';
import AttrController from '../../controller/attr';
import { isEqual } from '@antv/f-engine';
var AdjustMap = {
Stack: Stack,
Dodge: Dodge,
Jitter: Jitter,
Symmetric: Symmetric
};
// 保留原始数据的字段
var FIELD_ORIGIN = 'origin';
var Geometry = /** @class */function (_super) {
__extends(Geometry, _super);
function Geometry(props, context) {
var _this = _super.call(this, props, context) || this;
_this.isGeometry = true;
// x 轴居中
_this.justifyContent = false;
// y 轴是否从0开始
_this.startOnZero = false;
// 是否连接空值
_this.connectNulls = false;
// 是否需要排序
_this.sortable = false;
mix(_this, _this.getDefaultCfg());
var chart = props.chart,
coord = props.coord;
var attrsRange = _this._getThemeAttrsRange();
_this.attrController = new AttrController(chart.scale, attrsRange);
var _a = _this,
attrController = _a.attrController,
justifyContent = _a.justifyContent;
var attrOptions = attrController.getAttrOptions(props, !coord.isCyclic() || justifyContent);
attrController.create(attrOptions);
return _this;
}
Geometry.prototype.getDefaultCfg = function () {
return {};
};
Geometry.prototype.willReceiveProps = function (nextProps) {
var _a = this,
lastProps = _a.props,
attrController = _a.attrController,
justifyContent = _a.justifyContent;
var nextData = nextProps.data,
nextAdjust = nextProps.adjust,
coord = nextProps.coord,
selection = nextProps.selection;
var lastData = lastProps.data,
lastAdjust = lastProps.adjust,
lastSelection = lastProps.selection;
var justifyContentCenter = !coord.isCyclic() || justifyContent;
var lastAttrOptions = attrController.getAttrOptions(lastProps, justifyContentCenter);
attrController.attrsRange = this._getThemeAttrsRange();
var nextAttrOptions = attrController.getAttrOptions(nextProps, justifyContentCenter);
if (!isEqual(nextAttrOptions, lastAttrOptions)) {
attrController.update(nextAttrOptions);
this.dataRecords = null;
}
// 重新处理数据
if (nextData !== lastData) {
this.dataRecords = null;
}
// 重新处理数据
if (nextAdjust !== lastAdjust) {
this.dataRecords = null;
}
// selection 发生变化
if (!isEqual(selection, lastSelection)) {
_super.prototype.willReceiveProps.call(this, nextProps);
}
};
Geometry.prototype.willMount = function () {
this._createAttrs();
if (!this.dataRecords) {
this._processData();
}
};
Geometry.prototype.willUpdate = function () {
this._createAttrs();
if (!this.dataRecords) {
this._processData();
} else {
this._readjustData(this.dataRecords);
}
};
Geometry.prototype.didMount = function () {
this._initEvent();
_super.prototype.didMount.call(this);
// 更新 attrController
this.attrController.attrsRange = this._getThemeAttrsRange();
};
Geometry.prototype._initEvent = function () {
var _this = this;
var props = this.props;
var chart = props.chart;
['onPressStart', 'onPress', 'onPressEnd', 'onPan', 'onPanStart', 'onPanEnd'].forEach(function (eventName) {
if (props[eventName]) {
chart.on(eventName.substr(2).toLowerCase(), function (ev) {
ev.geometry = _this;
props[eventName](ev);
});
}
});
};
Geometry.prototype._createAttrs = function () {
var attrController = this.attrController;
attrController.attrs = {};
this.attrs = attrController.getAttrs();
};
Geometry.prototype._getThemeAttrsRange = function () {
var _a = this,
context = _a.context,
props = _a.props,
geomType = _a.geomType;
var coord = props.coord;
var theme = context.theme;
var colors = theme.colors,
sizes = theme.sizes,
shapes = theme.shapes;
return {
x: coord.x,
y: coord.y,
color: colors,
size: sizes,
shape: shapes[geomType]
};
};
Geometry.prototype._createAdjust = function () {
var _a = this,
attrs = _a.attrs,
props = _a.props;
var adjust = props.adjust;
if (!adjust) {
return null;
}
var adjustCfg = typeof adjust === 'string' ? {
type: adjust
} : adjust;
var adjustType = upperFirst(adjustCfg.type);
var AdjustConstructor = AdjustMap[adjustType];
if (!AdjustConstructor) {
throw new Error('not support such adjust : ' + adjust);
}
if (adjustType === 'Dodge') {
// @ts-ignore
adjustCfg.adjustNames = ['x'];
}
var x = attrs.x,
y = attrs.y;
// @ts-ignore
adjustCfg.xField = x.field;
// @ts-ignore
adjustCfg.yField = y.field;
var adjustInstance = new AdjustConstructor(adjustCfg);
this.adjust = {
type: adjustCfg.type,
adjust: adjustInstance
};
return this.adjust;
};
Geometry.prototype._adjustScales = function () {
var _a = this,
attrs = _a.attrs,
props = _a.props,
defaultStartOnZero = _a.startOnZero;
var chart = props.chart,
_b = props.startOnZero,
startOnZero = _b === void 0 ? defaultStartOnZero : _b,
coord = props.coord,
adjust = props.adjust;
var isPolar = coord.isPolar,
transposed = coord.transposed;
var y = attrs.y;
var yField = y.field;
// 如果从 0 开始,只调整 y 轴 scale
if (startOnZero) {
var y_1 = attrs.y;
chart.scale.adjustStartZero(y_1.scale);
}
// 饼图的scale调整,关闭nice
if (isPolar && transposed && (adjust === 'stack' || (adjust === null || adjust === void 0 ? void 0 : adjust.type) === 'stack')) {
var y_2 = attrs.y;
chart.scale.adjustPieScale(y_2.scale);
}
if (adjust === 'stack' || (adjust === null || adjust === void 0 ? void 0 : adjust.type) === 'stack') {
this._updateStackRange(yField, y.scale, this.dataArray);
}
};
Geometry.prototype._groupData = function (data) {
var attrController = this.attrController;
var groupScales = attrController.getGroupScales();
if (!groupScales.length) {
return [{
children: data
}];
}
var names = [];
groupScales.forEach(function (scale) {
var field = scale.field;
names.push(field);
});
var groups = groupToMap(data, names);
var records = [];
for (var key in groups) {
records.push({
key: key.replace(/^_/, ''),
children: groups[key]
});
}
return records;
};
Geometry.prototype._saveOrigin = function (originData) {
var _a;
var len = originData.length;
var data = new Array(len);
for (var i = 0; i < len; i++) {
var record = originData[i];
data[i] = __assign(__assign({}, record), (_a = {}, _a[FIELD_ORIGIN] = record, _a));
}
return data;
};
Geometry.prototype._numberic = function (data) {
var attrs = this.attrs;
var scales = [attrs.x.scale, attrs.y.scale];
for (var j = 0, len = data.length; j < len; j++) {
var obj = data[j];
var count = scales.length;
for (var i = 0; i < count; i++) {
var scale = scales[i];
if (scale.isCategory) {
var field = scale.field;
var value = scale.translate(obj.origin[field]);
obj[field] = value;
}
}
}
};
Geometry.prototype._adjustData = function (records) {
var adjust = this.adjust;
// groupedArray 是二维数组
var groupedArray = records.map(function (record) {
return record.children;
});
if (!adjust) {
return groupedArray;
}
var attrs = this.attrs;
var scales = [attrs.x.scale, attrs.y.scale];
for (var i = 0, len = groupedArray.length; i < len; i++) {
var records_1 = groupedArray[i];
for (var j = 0, len_1 = records_1.length; j < len_1; j++) {
var record = records_1[j];
var count = scales.length;
for (var i_1 = 0; i_1 < count; i_1++) {
var scale = scales[i_1];
var field = scale.field;
record[field] = record.origin[field];
}
}
}
if (adjust.type === 'dodge') {
for (var i = 0, len = groupedArray.length; i < len; i++) {
// 如果是dodge, 需要处理数字再处理
this._numberic(groupedArray[i]);
}
}
var adjustData = adjust.adjust.process(groupedArray);
// process 返回的是新数组,所以要修改 records
records.forEach(function (record, index) {
record.children = adjustData[index];
});
return adjustData;
};
Geometry.prototype._updateStackRange = function (field, scale, dataArray) {
var flattenArray = flatten(dataArray);
var min = Infinity;
var max = -Infinity;
for (var i = 0, len = flattenArray.length; i < len; i++) {
var obj = flattenArray[i];
var tmpMin = Math.min.apply(null, obj[field]);
var tmpMax = Math.max.apply(null, obj[field]);
if (tmpMin < min) {
min = tmpMin;
}
if (tmpMax > max) {
max = tmpMax;
}
}
if (min !== scale.min || max !== scale.max) {
scale.change({
min: min,
max: max
});
}
};
Geometry.prototype._processData = function () {
var props = this.props;
var originData = props.data;
var data = this._saveOrigin(originData);
// 根据分类度量进行数据分组
var records = this._groupData(data);
this._createAdjust();
// 根据adjust分组
var dataArray = this._adjustData(records);
this.dataArray = dataArray;
// scale适配调整,主要是调整 y 轴是否从 0 开始 以及 饼图
this._adjustScales();
// 数据排序(非必须)
if (this.sortable) {
this._sortData(records);
}
this.dataRecords = records;
};
Geometry.prototype._readjustData = function (records) {
var adjust = this.adjust;
if (!adjust) return;
// 根据adjust分组
var dataArray = this._adjustData(records);
this.dataArray = dataArray;
};
Geometry.prototype._sortData = function (records) {
var xScale = this.getXScale();
var field = xScale.field,
type = xScale.type;
if (type !== 'identity' && xScale.values.length > 1) {
each(records, function (_a) {
var children = _a.children;
children.sort(function (record1, record2) {
if (type === 'timeCat') {
return toTimeStamp(record1[FIELD_ORIGIN][field]) - toTimeStamp(record2[FIELD_ORIGIN][field]);
}
var normalized1 = xScale.translate(record1[FIELD_ORIGIN][field]);
var normalized2 = xScale.translate(record2[FIELD_ORIGIN][field]);
if (isNaN(normalized1)) {
return 1;
}
if (isNaN(normalized2)) {
return -1;
}
return normalized1 - normalized2;
});
});
}
};
Geometry.prototype.getY0Value = function () {
var _a = this,
attrs = _a.attrs,
props = _a.props;
var chart = props.chart;
var field = attrs.y.field;
var scale = chart.getScale(field);
return chart.scale.getZeroValue(scale);
};
// 根据各属性映射的值域来获取真正的绘图属性
Geometry.prototype._getShapeStyle = function (shape, origin) {
var _a = this,
context = _a.context,
props = _a.props,
geomType = _a.geomType;
var theme = context.theme;
var shapeTheme = theme.shape[geomType] || {};
var defaultShapeStyle = shapeTheme.default;
var shapeThemeStyle = shapeTheme[shape];
var style = props.style;
var shapeStyle = __assign(__assign({}, defaultShapeStyle), shapeThemeStyle);
if (!style || !isObject(style)) {
return shapeStyle;
}
// @ts-ignore
var field = style.field,
styles = __rest(style, ["field"]);
var value = field ? origin[field] : origin;
each(styles, function (attr, key) {
if (isFunction(attr)) {
var attrValue = attr(value);
if (!attrValue) {
return;
}
shapeStyle[key] = attrValue;
return;
}
shapeStyle[key] = attr;
});
return shapeStyle;
};
/**
* 数据映射到视图属性核心逻辑
* x、y 每个元素走 normalize 然后 convertPoint
* color、size、shape
* 如果是Linear,则每个元素 走 mapping
* 如果是Category/Identity 则第一个元素走 mapping
*/
Geometry.prototype._mapping = function (records) {
var _a = this,
attrs = _a.attrs,
props = _a.props,
attrController = _a.attrController;
var coord = props.coord;
var _b = attrController.getAttrsByLinear(),
linearAttrs = _b.linearAttrs,
nonlinearAttrs = _b.nonlinearAttrs;
var defaultAttrValues = attrController.getDefaultAttrValues();
var mappedRecords = [];
for (var i = 0, len = records.length; i < len; i++) {
var record = records[i];
var children = record.children;
var attrValues = __assign({}, defaultAttrValues);
var firstChild = children[0];
if (children.length === 0) {
mappedRecords.push(__assign({}, record));
continue;
}
// 非线性映射
for (var k = 0, len_2 = nonlinearAttrs.length; k < len_2; k++) {
var attrName = nonlinearAttrs[k];
var attr = attrs[attrName];
// 非线性映射只用映射第一项就可以了
attrValues[attrName] = attr.mapping(firstChild[attr.field], firstChild.origin);
}
// 线性属性映射
var mappedChildren = [];
for (var j = 0, childrenLen = children.length; j < childrenLen; j++) {
var child = children[j];
var normalized = {};
for (var k = 0; k < linearAttrs.length; k++) {
var attrName = linearAttrs[k];
var attr = attrs[attrName];
var value = child[attr.field];
// 分类属性的线性映射
if (attrController.isGroupAttr(attrName)) {
attrValues[attrName] = attr.mapping(value, child);
} else {
normalized[attrName] = attr.normalize(value);
}
}
var _c = coord.convertPoint({
x: normalized.x,
y: normalized.y
}),
x = _c.x,
y = _c.y;
// 获取 shape 的 style
var origin_1 = child.origin;
var shapeName = attrValues.shape;
var shape = this._getShapeStyle(shapeName, origin_1);
var selected = this.isSelected(child);
mappedChildren.push(__assign(__assign(__assign({}, child), attrValues), {
normalized: normalized,
x: x,
y: y,
shapeName: shapeName,
shape: shape,
selected: selected
}));
}
mappedRecords.push(__assign(__assign({}, record), {
children: mappedChildren
}));
}
return mappedRecords;
};
// 数据映射
Geometry.prototype.mapping = function () {
var dataRecords = this.dataRecords;
// 数据映射
this.records = this._mapping(dataRecords);
return this.records;
};
Geometry.prototype.getClip = function () {
var _a = this.props,
coord = _a.coord,
viewClip = _a.viewClip;
var contentWidth = coord.width,
contentHeight = coord.height,
left = coord.left,
top = coord.top;
if (viewClip) {
return {
type: 'rect',
style: {
x: left,
y: top,
width: contentWidth,
height: contentHeight
}
};
}
return null;
};
Geometry.prototype.getAttr = function (attrName) {
return this.attrController.getAttr(attrName);
};
Geometry.prototype.getXScale = function () {
return this.getAttr('x').scale;
};
Geometry.prototype.getYScale = function () {
return this.getAttr('y').scale;
};
Geometry.prototype._getXSnap = function (invertPointX) {
var xScale = this.getXScale();
if (xScale.isCategory) {
return xScale.invert(invertPointX);
}
// linear 类型
var invertValue = xScale.invert(invertPointX);
var values = xScale.values;
var len = values.length;
// 如果只有1个点直接返回第1个点
if (len === 1) {
return values[0];
}
// 第1个点和第2个点之间
if ((values[0] + values[1]) / 2 > invertValue) {
return values[0];
}
// 最后2个点
if ((values[len - 2] + values[len - 1]) / 2 <= invertValue) {
return values[len - 1];
}
for (var i = 1; i < len; i++) {
// 中间的点
if ((values[i - 1] + values[i]) / 2 <= invertValue && (values[i + 1] + values[i]) / 2 > invertValue) {
return values[i];
}
}
return null;
};
Geometry.prototype._getYSnapRecords = function (invertPointY, records) {
var yScale = this.getYScale();
var yField = yScale.field;
var yValue = yScale.invert(invertPointY);
// category
if (yScale.isCategory) {
return records.filter(function (record) {
return record[FIELD_ORIGIN][yField] === yValue;
});
}
// linear
return records.filter(function (record) {
var rangeY = record[yField];
if (rangeY[0] <= yValue && rangeY[1] >= yValue) {
return true;
}
return false;
});
};
// 把 records 拍平
Geometry.prototype.flatRecords = function () {
var records = this.records;
return records.reduce(function (prevRecords, record) {
return prevRecords.concat(record.children);
}, []);
};
Geometry.prototype.getSnapRecords = function (point, inCoordRange) {
var props = this.props;
var coord = props.coord,
adjust = props.adjust;
var invertPoint = coord.invertPoint(point);
var xScale = this.getXScale();
var yScale = this.getYScale();
// 如果不在coord坐标范围内,直接返回空
// if (invertPoint.x < 0 || invertPoint.y < 0) {
// return [];
// }
// 是否调整 point,默认为不调整
if (inCoordRange) {
var xRange = xScale.range;
var yRange = yScale.range;
// 如果 inCoordRange=true,当 point 不在 coord 坐标范围内时,调整到 range 内
invertPoint.x = Math.min(Math.max(invertPoint.x, xRange[0]), xRange[1]);
invertPoint.y = Math.min(Math.max(invertPoint.y, yRange[0]), yRange[1]);
}
var records = this.flatRecords();
var xValue = xScale.invert(invertPoint.x);
var yValue = yScale.invert(invertPoint.y);
var coordPoint = coord.convertPoint(invertPoint);
var coordRecord = {
// 坐标点
x: coordPoint.x,
y: coordPoint.y,
xValue: xValue,
yValue: yValue,
xText: xScale.getText(xValue),
yText: yScale.getText(yValue)
};
// 处理饼图
if (adjust === 'stack' && coord.isPolar && coord.transposed) {
// 弧度在半径范围内
if (invertPoint.x >= 0 && invertPoint.x <= 1) {
var snapRecords = this._getYSnapRecords(invertPoint.y, records);
return snapRecords;
}
}
var rst = [];
var value = this._getXSnap(invertPoint.x);
if (isNull(value)) {
return rst;
}
var xField = xScale.field;
var yField = yScale.field;
for (var i = 0, len = records.length; i < len; i++) {
var record = __assign(__assign({}, records[i]), {
xField: xField,
yField: yField,
coord: coordRecord
});
var originValue = record[FIELD_ORIGIN][xField];
if (xScale.type === 'timeCat' && toTimeStamp(originValue) === value) {
rst.push(record);
} else if (originValue === value) {
rst.push(record);
}
}
return rst;
};
Geometry.prototype.getRecords = function (data, field) {
if (field === void 0) {
field = 'xfield';
}
var records = this.flatRecords();
var xScale = this.getXScale();
var yScale = this.getYScale();
var xField = xScale.field;
var yField = yScale.field;
var value = data[xField];
var rst = [];
for (var i = 0, len = records.length; i < len; i++) {
var record = __assign(__assign({}, records[i]), {
xField: xField,
yField: yField
});
var originValue = record[FIELD_ORIGIN][field === 'xfield' ? xField : yField];
if (originValue === value) {
rst.push(record);
}
}
return rst;
};
Geometry.prototype.getLegendItems = function () {
var _a = this,
attrController = _a.attrController,
records = _a.records;
var colorAttr = attrController.getAttr('color');
if (!colorAttr) return null;
var scale = colorAttr.scale;
var isCategory = scale.isCategory,
field = scale.field;
if (!isCategory) return null;
var flatRecords = records ? this.flatRecords() : [];
var ticks = scale.getTicks();
var items = ticks.map(function (tick) {
var text = tick.text,
tickValue = tick.tickValue;
var record = find(flatRecords, function (item) {
if (!item) return false;
var origin = item.origin;
return origin[field] === tickValue;
});
// @ts-ignore
var color = record ? record.color : colorAttr.mapping(tickValue);
return {
field: scale.field,
color: color,
name: text,
tickValue: tickValue
};
});
return items;
};
return Geometry;
}(Selection);
export default Geometry;