Skip to content

Commit

Permalink
feat(attr): add attribute class
Browse files Browse the repository at this point in the history
  • Loading branch information
逍为 committed Apr 25, 2021
1 parent 5af9a5a commit b4b05d2
Show file tree
Hide file tree
Showing 7 changed files with 242 additions and 26 deletions.
127 changes: 120 additions & 7 deletions src/geometry/attribute/attribute.ts
@@ -1,18 +1,131 @@
export type AttributeCfg = {};
import { isArray, isNil, isString } from '@antv/util';

export type CallbackFunc = (...args: any[]) => any[];

export type AttributeCfg = {
/**
* 对应字段
*/
readonly fields: string[]; // 可以直接 = scale.field 的值
/**
* 对应字段的 scales
*/
readonly scales: Scale[];
/**
* 属性映射的 value
*/
readonly value?: any;
/**
* 属性映射的 function,和 value 2 选 1
*/
readonly callback?: CallbackFunc;
};

type Scale = any;

/**
* 所有视觉通道属性的基类
* @class Base
*/
export class Attribute {
/**
* 类型
* attribute 的类型
*/
public type: string;
public type: string = 'base';
/**
* 字段信息
*/
public fields: string[];
/**
* 映射的值范围
*/
public value: any[] = [];
/**
* 属性映射的 callback 函数
*/
public callback: CallbackFunc;
/**
* 属性映射对应的字段 scale
*/
public scales: Scale[];
/**
* 是否是 linear 线性映射
*/
public isLinear: boolean = false;

constructor(cfg: AttributeCfg) {
this.update(cfg);
}

/**
* 属性中包含的 scale 类
* 映射的值组成的数组
* @param params 对应 scale 顺序的值传入
*/
public scales: any[];
public mapping(...params: any[]): any[] {
// 使用 callback 进行自定义映射
if (this.callback) {
// 当用户设置的 callback 返回 null 时, 应该返回默认 callback 中的值
const ret = this.callback(...params);
if (!isNil(ret)) {
return [ret];
}
}

// 没有 callback 或者用户 callback 返回值为空,则使用默认的逻辑处理
// 根据 value 来进行映射

// 没有 params 的情况,是指没有指定 fields,直接返回配置的 values 常量
if (this.fields.length === 0) {
return this.value;
}

return params.map((param, idx) => {
const scale = this.scales[idx];

// 线性的,则使用 linear value
if (this.isLinear) {
// 线性则使用线性值
const percent = scale.scale(param);
return this.getLinearValue(percent);
}

// 如果是非线性的字段,直接从 values 中取值即可
// 离散 scale 变换成索引
const scaleValue = scale.scale(param) as number;
return this.value[scaleValue % this.value.length];
});
}

/**
* 映射数据
* 更新配置
* @param cfg
*/
public mapping(...params: any) {}
public update(cfg: AttributeCfg) {
const { fields = [], scales = [], value = [], callback } = cfg;

this.fields = fields;
this.value = value;
this.callback = callback;

this.scales = scales;
}

/**
* 如果进行线性映射,返回对应的映射值
* @param percent
*/
protected getLinearValue(percent: number): number | string {
// 分段数量
const steps = this.value.length - 1;

const step = Math.floor(steps * percent);
const leftPercent = steps * percent - step;

// todo 不懂这个逻辑
const start = this.value[step];
const end = step === steps ? start : this.value[step + 1];

// 线性方程
return start + (end - start) * leftPercent;
}
}
47 changes: 45 additions & 2 deletions src/geometry/attribute/color.ts
@@ -1,5 +1,48 @@
import colorUtil from '@antv/color-util';
import { isString } from '@antv/util';
import { Attribute, AttributeCfg } from './attribute';

export type ColorAttributeCfg = AttributeCfg & {};
/**
* 映射的映射,可以根据索引映射,也可以根据渐变连续映射
*/
export class Color extends Attribute {
/**
* @override attribute 类型
*/
public type: string = 'color';

export class Color extends Attribute {}
/**
* 用于缓存,提升性能
*/
private gradientFunc: (percent: number) => string;

constructor(cfg: AttributeCfg) {
super(cfg);

// value 参数决定是否是线性的
// 其实可以根据 scale 的 type 来决定(避免信息的丢失)
this.isLinear = isString(this.value);
}

/**
* @override
*/
protected getLinearValue(percent: number): string {
// 不存在则创建一个,并缓存起来
if (!this.gradientFunc) {
this.gradientFunc = colorUtil.gradient(this.value);
}

return this.gradientFunc(percent);
}

/**
* @override
*/
public update(cfg: AttributeCfg) {
super.update(cfg);

// 额外需要清空缓存
this.gradientFunc = undefined;
}
}
19 changes: 16 additions & 3 deletions src/geometry/attribute/indentity.ts
@@ -1,5 +1,18 @@
import { Attribute, AttributeCfg } from './attribute';
import { Attribute } from './attribute';

export type IndentityAttributeCfg = AttributeCfg & {};
/**
* 对应 indentity 的映射,不做任何事情!
*/
export class Indentity extends Attribute {
/**
* @override attribute 类型
*/
public type: string = 'indentity';

export class Indentity extends Attribute {}
/**
* @override 不做任何事情,直接返回
*/
public mapping(...params: any[]) {
return params;
}
}
12 changes: 7 additions & 5 deletions src/geometry/attribute/index.ts
Expand Up @@ -2,8 +2,10 @@
* 视觉属性映射的实例
*/

export { Color, ColorAttributeCfg } from './color';
export { Indentity, IndentityAttributeCfg } from './indentity';
export { Position, PositionAttributeCfg } from './position';
export { Shape, ShapeAttributeCfg } from './shape';
export { Size, SizeAttributeCfg } from './size';
export { Attribute, AttributeCfg } from './attribute';

export { Color } from './color';
export { Indentity } from './indentity';
export { Position } from './position';
export { Shape } from './shape';
export { Size } from './size';
33 changes: 30 additions & 3 deletions src/geometry/attribute/position.ts
@@ -1,5 +1,32 @@
import { Attribute, AttributeCfg } from './attribute';
import { isArray, isNil } from '@antv/util';
import { Attribute } from './attribute';

export type PositionAttributeCfg = AttributeCfg & {};
type PositionValue = number | string;

export class Position extends Attribute {}
/**
* 位置 x y 通道的映射
*/
export class Position extends Attribute {
/**
* @override attribute 类型
*/
public type: string = 'position';

/**
* @override 重写映射函数,直接使用 scale 记性转换
* @param x
* @param y
*/
public mapping(x: PositionValue, y: PositionValue) {
const [scaleX, scaleY] = this.scales;

if (isNil(x) || isNil(y)) {
return [];
}

return [
isArray(x) ? x.map((xi) => scaleX.scale(xi)) : scaleX.scale(x),
isArray(y) ? y.map((yi) => scaleY.scale(yi)) : scaleY.scale(y),
];
}
}
18 changes: 15 additions & 3 deletions src/geometry/attribute/shape.ts
@@ -1,5 +1,17 @@
import { Attribute, AttributeCfg } from './attribute';
import { Attribute } from './attribute';

export type ShapeAttributeCfg = AttributeCfg & {};
export class Shape extends Attribute {
/**
* @override attribute 类型
*/
public type: string = 'shape';

export class Shape extends Attribute {}
/**
* @override
*/
public getLinearValue(percent: number): string {
// shape 的连续映射,其实也是根据分类来的!
const idx = Math.round((this.value.length - 1) * percent);
return this.value[idx];
}
}
12 changes: 9 additions & 3 deletions src/geometry/attribute/size.ts
@@ -1,5 +1,11 @@
import { Attribute, AttributeCfg } from './attribute';

export type SizeAttributeCfg = AttributeCfg & {};

export class Size extends Attribute {}
/**
* 尺寸 size 的映射,可以是分类的,也可以是连续的
*/
export class Size extends Attribute {
/**
* @override attribute 类型
*/
public type: string = 'size';
}

0 comments on commit b4b05d2

Please sign in to comment.