Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(attr): add attribute class #3391

Merged
merged 6 commits into from
Apr 26, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 11 additions & 0 deletions __tests__/unit/visual/attribute/color-spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import { Color } from '../../../../src/visual/attribute';

describe('attribute color', () => {
it('value', () => {
expect(Color).toBeDefined();
});

it('callback', () => {
expect(Color).toBeDefined();
});
});
8 changes: 8 additions & 0 deletions __tests__/unit/visual/attribute/index-spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
import { Attribute, Color, Position, Shape, Size } from '../../../../src/visual/attribute';

// TODO 单测 100%
describe('attribute', () => {
it('export', () => {
expect([Attribute, Color, Position, Shape, Size].every((e) => !!e)).toBe(true);
});
});
11 changes: 11 additions & 0 deletions __tests__/unit/visual/attribute/position-spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import { Position } from '../../../../src/visual/attribute';

describe('attribute position', () => {
it('value', () => {
expect(Position).toBeDefined();
});

it('callback', () => {
expect(Position).toBeDefined();
});
});
11 changes: 11 additions & 0 deletions __tests__/unit/visual/attribute/shape-spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import { Shape } from '../../../../src/visual/attribute';

describe('attribute shape', () => {
it('value', () => {
expect(Shape).toBeDefined();
});

it('callback', () => {
expect(Shape).toBeDefined();
});
});
11 changes: 11 additions & 0 deletions __tests__/unit/visual/attribute/size-spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import { Size } from '../../../../src/visual/attribute';

describe('attribute color', () => {
it('value', () => {
expect(Size).toBeDefined();
});

it('callback', () => {
expect(Size).toBeDefined();
});
});
18 changes: 0 additions & 18 deletions src/geometry/attribute/attribute.ts

This file was deleted.

5 changes: 0 additions & 5 deletions src/geometry/attribute/color.ts

This file was deleted.

5 changes: 0 additions & 5 deletions src/geometry/attribute/indentity.ts

This file was deleted.

9 changes: 0 additions & 9 deletions src/geometry/attribute/index.ts

This file was deleted.

5 changes: 0 additions & 5 deletions src/geometry/attribute/position.ts

This file was deleted.

5 changes: 0 additions & 5 deletions src/geometry/attribute/shape.ts

This file was deleted.

5 changes: 0 additions & 5 deletions src/geometry/attribute/size.ts

This file was deleted.

2 changes: 1 addition & 1 deletion src/geometry/geometry/geometry.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ import { GROUP_ATTR_KEYS, ORIGINAL_FIELD } from '../../constant';
import { createAttribute } from '../../util/attribute';
import { groupData } from '../../util/data';
import { getScaleUpdateOptionsAfterStack } from '../../util/scale';
import { Attribute } from '../attribute/attribute';
import { Attribute } from '../../visual/attribute';
import { Element } from '../element';
import { isArray } from '@antv/util';

Expand Down
12 changes: 6 additions & 6 deletions src/util/attribute.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { Color, Indentity, Position, Shape, Size } from '../geometry/attribute';
import { Color, Indentity, Position, Shape, Size } from '../visual/attribute';

/**
* 创建一个 attribute 类型的实例
Expand All @@ -9,14 +9,14 @@ import { Color, Indentity, Position, Shape, Size } from '../geometry/attribute';
export function createAttribute(type: string, cfg: any) {
switch (type) {
case 'color':
return new Color();
return new Color(cfg);
case 'position':
return new Position();
return new Position(cfg);
case 'shape':
return new Shape();
return new Shape(cfg);
case 'size':
return new Size();
return new Size(cfg);
default:
return new Indentity();
return new Indentity(cfg);
}
}
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
131 changes: 131 additions & 0 deletions src/visual/attribute/attribute.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,131 @@
import { isNil } 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 = '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);
}

/**
* 映射的值组成的数组
* @param params 对应 scale 顺序的值传入
*/
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 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;
}
}
48 changes: 48 additions & 0 deletions src/visual/attribute/color.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
import colorUtil from '@antv/color-util';
import { isString } from '@antv/util';
import { Attribute, AttributeCfg } from './attribute';

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

/**
* 用于缓存,提升性能
*/
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;
}
}
18 changes: 18 additions & 0 deletions src/visual/attribute/indentity.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import { Attribute } from './attribute';

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

/**
* @override 不做任何事情,直接返回
*/
public mapping(...params: any[]) {
return params;
}
}
11 changes: 11 additions & 0 deletions src/visual/attribute/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
/**
* 视觉属性映射的实例
*/

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';
32 changes: 32 additions & 0 deletions src/visual/attribute/position.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
import { isArray, isNil } from '@antv/util';
import { Attribute } from './attribute';

type PositionValue = number | string;

/**
* 位置 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),
];
}
}
Loading