Skip to content

Commit

Permalink
refactor: 优化代码
Browse files Browse the repository at this point in the history
  • Loading branch information
zhangzhonghe committed Oct 22, 2020
1 parent e5e99da commit e87f77c
Show file tree
Hide file tree
Showing 6 changed files with 87 additions and 123 deletions.
99 changes: 25 additions & 74 deletions src/plots/word-cloud/adaptor.ts
@@ -1,6 +1,8 @@
import { deepMix } from '@antv/util';
import { Params } from '../../core/adaptor';
import { tooltip, interaction, animation, theme, scale, state } from '../../adaptor/common';
import { flow, findGeometry } from '../../utils';
import { flow } from '../../utils';
import { point } from '../../adaptor/geometries';
import { WordCloudOptions } from './types';
import { transform } from './utils';

Expand All @@ -9,31 +11,32 @@ import { transform } from './utils';
* @param params
*/
function geometry(params: Params<WordCloudOptions>): Params<WordCloudOptions> {
const { chart } = params;
const { chart, options } = params;
const { colorField, color } = options;
const data = transform(params);

chart.data(data);
chart.point().position('x*y').shape('word-cloud');

return params;
}

/**
* color 配置处理
* @param params
*/
function color(params: Params<WordCloudOptions>): Params<WordCloudOptions> {
const { chart, options } = params;
const { color, colorField, wordField, weightField } = options;
const geometry = findGeometry(chart, 'point');
// 最终的数据中 wordField 和 weightField 对应的字段值
// 会转换成 'text' 和 'value'
const fieldMap = {
[wordField]: 'text',
[weightField]: 'value',
};
const p = deepMix({}, params, {
options: {
xField: 'x',
yField: 'y',
// 给 seriesField 一个默认值,否则它为空时
// 每个词语的颜色会显示成白色。
seriesField: colorField ? 'color' : 'text',
point: {
color,
shape: 'word-cloud',
},
},
});

const { ext } = point(p);
ext.geometry.label(false);

geometry.color(colorField ? fieldMap[colorField] || colorField : 'text', color);
chart.coordinate().reflect('y');
chart.axis(false);
chart.legend(false);

return params;
}
Expand All @@ -51,64 +54,12 @@ function meta(params: Params<WordCloudOptions>): Params<WordCloudOptions> {
)(params);
}

/**
* coord 配置
* @param params
*/
function coord(params: Params<WordCloudOptions>): Params<WordCloudOptions> {
const { chart } = params;

chart.coordinate().reflect('y');

return params;
}

/**
* axis 配置
* 词云图不显示轴信息
* @param params
*/
function axis(params: Params<WordCloudOptions>): Params<WordCloudOptions> {
const { chart } = params;

chart.axis(false);

return params;
}

/**
* label 配置
* 词云图不显示 label 信息
* @param params
*/
function label(params: Params<WordCloudOptions>): Params<WordCloudOptions> {
const { chart } = params;
const geometry = findGeometry(chart, 'point');

geometry.label(false);

return params;
}

/**
* legend 配置
* 词云图不显示 legend 信息
* @param params
*/
function legend(params: Params<WordCloudOptions>): Params<WordCloudOptions> {
const { chart } = params;

chart.legend(false);

return params;
}

/**
* 词云图适配器
* @param chart
* @param options
*/
export function adaptor(params: Params<WordCloudOptions>) {
// flow 的方式处理所有的配置到 G2 API
flow(geometry, meta, coord, axis, label, color, legend, tooltip, interaction, animation, theme, state)(params);
flow(geometry, meta, tooltip, interaction, animation, theme, state)(params);
}
4 changes: 2 additions & 2 deletions src/plots/word-cloud/index.ts
@@ -1,7 +1,7 @@
import { deepMix } from '@antv/util';
import { Plot } from '../../core/plot';
import { Adaptor } from '../../core/adaptor';
import { DataItem, WordCloudOptions } from './types';
import { Tag, WordCloudOptions } from './types';
import { adaptor } from './adaptor';
import { processImageMask } from './utils';
// 注册的shape
Expand All @@ -23,7 +23,7 @@ export class WordCloud extends Plot<WordCloudOptions> {
showTitle: false,
showMarkers: false,
showCrosshairs: false,
customContent(_, data: { data: DataItem; mappingData: { color: string } }[]) {
customContent(_, data: { data: Tag; mappingData: { color: string } }[]) {
if (!data.length) return;
// 不完全采用模板字符串,是为了去掉换行符和部分空格,
// 便于测试。
Expand Down
4 changes: 2 additions & 2 deletions src/plots/word-cloud/shapes/word-cloud.ts
@@ -1,9 +1,9 @@
import { registerShape, Util } from '@antv/g2';
import { ShapeInfo } from '@antv/g2/lib/interface';
import { IGroup, ShapeAttrs } from '@antv/g2/lib/dependents';
import { DataItem } from '../types';
import { Tag } from '../types';

type Config = ShapeInfo & { data: DataItem };
type Config = ShapeInfo & { data: Tag };

registerShape('point', 'word-cloud', {
draw(cfg: Config, group: IGroup) {
Expand Down
19 changes: 12 additions & 7 deletions src/plots/word-cloud/types.ts
@@ -1,16 +1,21 @@
import { ShapeAttrs } from '@antv/g2/lib/dependents';
import { Options } from '../../types';
import { Datum, Options } from '../../types';

type FontWeight = ShapeAttrs['fontWeight'];

interface Row {
/** 一个文本信息,wordCloud 内部 */
export interface Word {
/** 文本内容 */
text: string;
/** 该文本所占权重 */
value: number;
/** 用于指定颜色字段 */
color: string | number;
/** 原始数据 */
datum: Datum;
}

export type DataItem = Row & {
export type Tag = Word & {
/** 字体 */
font?: string;
/** 字体样式 */
Expand Down Expand Up @@ -38,16 +43,16 @@ export type DataItem = Row & {
/** 词云字体样式 */
interface WordStyle {
/** 词云的字体, 当为函数时,其参数是一个经过处理之后的数据元素的值 */
readonly fontFamily?: string | ((row: Row) => string);
readonly fontFamily?: string | ((word: Word) => string);
/** 设置字体的粗细, 当为函数时,其参数是一个经过处理之后的数据元素的值 */
readonly fontWeight?: FontWeight | ((row: Row) => FontWeight);
readonly fontWeight?: FontWeight | ((word: Word) => FontWeight);
/**
* 每个单词所占的盒子的内边距,默认为 1。 越大单词之间的间隔越大。
* 当为函数时,其参数是一个经过处理之后的数据元素的值
*/
readonly padding?: number | ((row: Row) => number);
readonly padding?: number | ((word: Word) => number);
/** 字体的大小范围,当为函数时,其参数是一个经过处理之后的数据元素的值 */
readonly fontSize?: [number, number] | ((row: Row) => number);
readonly fontSize?: [number, number] | ((word: Word) => number);
/** 旋转的最小角度和最大角度 默认 [0, 90] */
readonly rotation?: [number, number];
/** 旋转实际的步数,越大可能旋转角度越小, 默认是 2 */
Expand Down
26 changes: 21 additions & 5 deletions src/plots/word-cloud/utils.ts
@@ -1,26 +1,42 @@
import { Chart, View } from '@antv/g2';
import { isArray, isFunction, isNumber, isString } from '@antv/util';
import { Params } from '../../core/adaptor';
import { Datum } from '../../types';
import { log, LEVEL, getContainerSize } from '../../utils';
import { wordCloud } from '../../utils/transform/word-cloud';
import { DataItem, WordCloudOptions } from './types';
import { Tag, Word, WordCloudOptions } from './types';

/**
* 用 DataSet 转换词云图数据
* @param params
*/
export function transform(params: Params<WordCloudOptions>): DataItem[] {
export function transform(params: Params<WordCloudOptions>): Tag[] {
const { options } = params;
const { data, imageMask, wordField, weightField, wordStyle, timeInterval } = options;
const { data, imageMask, wordField, weightField, colorField, wordStyle, timeInterval } = options;
if (!data || !data.length) {
return [];
}
const { fontFamily, fontWeight, padding } = wordStyle;
const arr = data.map((v) => v[weightField]) as number[];
const range = [min(arr), max(arr)] as [number, number];

return wordCloud(data, {
fields: [wordField, weightField],
// 校验
if (!isString(wordField) || !isString(weightField)) {
throw new TypeError('Invalid fields: must be an array with 2 strings (e.g. [ "text", "value" ])!');
}

// 变换出 text 和 value 字段
const words = data.map(
(datum: Datum): Word => ({
text: datum[wordField],
value: datum[weightField],
color: datum[colorField],
datum, // 存一下原始数据
})
);

// 数据准备在外部做,wordCloud 单纯就是做布局
return wordCloud(words, {
imageMask: imageMask as HTMLImageElement,
font: fontFamily,
fontSize: getFontSize(options, range),
Expand Down
58 changes: 25 additions & 33 deletions src/utils/transform/word-cloud.ts
@@ -1,31 +1,21 @@
import { deepMix, assign, isString } from '@antv/util';
import { DataItem } from '../../plots/word-cloud/types';
import { Data } from '../../types';
import { isNil, assign } from '@antv/util';
import { Tag, Word } from '../../plots/word-cloud/types';

type FontWeight = number | 'normal' | 'bold' | 'bolder' | 'lighter';

interface Row {
/** 文本内容 */
text: string;
/** 该文本所占权重 */
value: number;
}

export interface Options {
fields: [string, string];
size: [number, number];
font?: string | ((row: Row) => string);
fontSize?: number | ((row: Row) => number);
fontWeight?: FontWeight | ((row: Row) => FontWeight);
rotate?: number | ((row: Row) => number);
padding?: number | ((row: Row) => number);
font?: string | ((row: Word) => string);
fontSize?: number | ((row: Word) => number);
fontWeight?: FontWeight | ((row: Word) => FontWeight);
rotate?: number | ((row: Word) => number);
padding?: number | ((row: Word) => number);
spiral?: 'archimedean' | 'rectangular' | ((size: [number, number]) => (t: number) => number[]);
timeInterval?: number;
imageMask?: HTMLImageElement;
}

const DEFAULT_OPTIONS: Options = {
fields: ['text', 'value'], // fields to keep
font: () => 'serif',
padding: 1,
size: [500, 500],
Expand All @@ -39,45 +29,47 @@ const DEFAULT_OPTIONS: Options = {
* 根据对应的数据对象,计算每个
* 词语在画布中的渲染位置,并返回
* 计算后的数据对象
* @param words
* @param options
*/
export function wordCloud(data: Data, options?: Partial<Options>) {
export function wordCloud(words: Word[], options?: Partial<Options>): Tag[] {
// 混入默认配置
options = assign({} as Options, DEFAULT_OPTIONS, options);
return transform(data, options as Options);
return transform(words, options as Options);
}

export function transform(data: Data, options: Options) {
// 深拷贝
data = deepMix([], data);
/**
* 抛出没有混入默认配置的方法,用于测试。
* @param words
* @param options
*/
export function transform(words: Word[], options: Options) {
// 布局对象
const layout = tagCloud();
['font', 'fontSize', 'fontWeight', 'padding', 'rotate', 'size', 'spiral', 'timeInterval'].forEach((key) => {
if (options[key] !== undefined && options[key] !== null) {
['font', 'fontSize', 'fontWeight', 'padding', 'rotate', 'size', 'spiral', 'timeInterval'].forEach((key: string) => {
if (!isNil(options[key])) {
layout[key](options[key]);
}
});
const [text, value] = options.fields;
if (!isString(text) || !isString(value)) {
throw new TypeError('Invalid fields: must be an array with 2 strings (e.g. [ "text", "value" ])!');
}
const words = data.map((row) => ({
text: row[text],
value: row[value],
}));

layout.words(words);
if (options.imageMask) {
layout.createMask(options.imageMask);
}

const result = layout.start();
const tags: any[] = result._tags;

const bounds = result._bounds || [
{ x: 0, y: 0 },
{ x: options.size[0], y: options.size[1] },
];

tags.forEach((tag) => {
tag.x += options.size[0] / 2;
tag.y += options.size[1] / 2;
});

const [w, h] = options.size;
const hasImage = result.hasImage;
tags.push({
Expand All @@ -95,7 +87,7 @@ export function transform(data: Data, options: Options) {
opacity: 0,
});

return tags as DataItem[];
return tags;
}

/*
Expand Down

0 comments on commit e87f77c

Please sign in to comment.