@antv/attr
Version:
The Attribute module for G2, F2.
168 lines (141 loc) • 4.41 kB
text/typescript
import { each, identity, isArray, isNil, isString, mix } from '@antv/util';
import { AttributeCfg, CallbackType, Scale } from '../interface';
// todo 这个到底目的是什么?
const toScaleString = (scale: Scale, value: any): any => {
if (isString(value)) {
return value;
}
return scale.invert(scale.scale(value));
};
export type AttributeConstructor = new (cfg: any) => Attribute;
/**
* 所有视觉通道属性的基类
* @class Base
*/
export default class Attribute {
public type: string;
public names: string[] = [];
public scales: Scale[] = [];
public linear: boolean = false;
public values: any[] = [];
constructor(cfg: AttributeCfg) {
// 解析配置
this._parseCfg(cfg);
}
public callback: CallbackType = () => [];
/**
* 映射的值组成的数组
* @param params 对应 scale 顺序的值传入
*/
public mapping(...params: any[]): any[] {
const values = params.map((param, idx) => {
return this._toOriginParam(param, this.scales[idx]);
});
return this.callback.apply(this, values);
}
/**
* 如果进行线性映射,返回对应的映射值
* @param percent
*/
public getLinearValue(percent: number): number | string {
// 分段数量
const steps = this.values.length - 1;
const step = Math.floor(steps * percent);
const leftPercent = steps * percent - step;
// todo 不懂这个逻辑
const start = this.values[step];
const end = step === steps ? start : this.values[step + 1];
// 线性方程
return start + (end - start) * leftPercent;
}
/**
* 根据度量获取属性名
*/
public getNames() {
const scales = this.scales;
const names = this.names;
const length = Math.min(scales.length, names.length);
const rst = [];
for (let i = 0; i < length; i += 1) {
rst.push(names[i]);
}
return rst;
}
/**
* 获取所有的维度名
*/
public getFields() {
return this.scales.map((scale) => scale.field);
}
/**
* 根据名称获取度量
* @param name
*/
public getScale(name: string) {
return this.scales[this.names.indexOf(name)];
}
/**
* 默认的回调函数(用户没有自定义 callback,或者用户自定义 callback 返回空的时候,使用 values 映射)
* @param params
*/
private defaultCallback(...params: any[]): any[] {
// 没有 params 的情况,是指没有指定 fields,直接返回配置的 values 常量
if (params.length === 0) {
return this.values;
}
return params.map((param, idx) => {
const scale = this.scales[idx];
return scale.type === 'identity' ? scale.values[0] : this._getAttributeValue(scale, param);
});
}
// 解析配置
private _parseCfg(cfg: AttributeCfg) {
const { type = 'base', names = [], scales = [], values = [], callback } = cfg;
this.type = type;
this.scales = scales;
this.values = values;
this.names = names;
// 构造 callback 方法
this.callback = (...params: any[]): any[] => {
/**
* 当用户设置的 callback 返回 null 时, 应该返回默认 callback 中的值
*/
if (callback) {
// 使用用户返回的值处理
const ret = callback(...params);
if (!isNil(ret)) {
return [ret];
}
}
// 没有 callback 或者用户 callback 返回值为空,则使用默认的逻辑处理
return this.defaultCallback.apply(this, params);
};
}
// 获取属性值,将值映射到视觉通道
private _getAttributeValue(scale: Scale, value: any) {
// 如果是非线性的字段,直接从 values 中取值即可
if (scale.isCategory && !this.linear) {
// 离散 scale 变换成索引
const idx = scale.translate(value) as number;
return this.values[idx % this.values.length];
}
// 线性则使用线性值
const percent = scale.scale(value);
return this.getLinearValue(percent);
}
/**
* 通过 scale 拿到数据对应的原始的参数
* @param param
* @param scale
* @private
*/
private _toOriginParam(param: any, scale: Scale) {
// 是线性,直接返回
// 非线性,使用 scale 变换
return !scale.isLinear
? isArray(param)
? param.map((p: any) => toScaleString(scale, p))
: toScaleString(scale, param)
: param;
}
}