From e87f77c0659a6bffabe16ebde3110abe14ae533f Mon Sep 17 00:00:00 2001 From: zhangzhonghe <958414905@qq.com> Date: Thu, 22 Oct 2020 16:45:39 +0800 Subject: [PATCH] =?UTF-8?q?refactor:=20=E4=BC=98=E5=8C=96=E4=BB=A3?= =?UTF-8?q?=E7=A0=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/plots/word-cloud/adaptor.ts | 99 ++++++----------------- src/plots/word-cloud/index.ts | 4 +- src/plots/word-cloud/shapes/word-cloud.ts | 4 +- src/plots/word-cloud/types.ts | 19 +++-- src/plots/word-cloud/utils.ts | 26 ++++-- src/utils/transform/word-cloud.ts | 58 ++++++------- 6 files changed, 87 insertions(+), 123 deletions(-) diff --git a/src/plots/word-cloud/adaptor.ts b/src/plots/word-cloud/adaptor.ts index 287e4fc7a0d..be3b23b8354 100644 --- a/src/plots/word-cloud/adaptor.ts +++ b/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'; @@ -9,31 +11,32 @@ import { transform } from './utils'; * @param params */ function geometry(params: Params): Params { - 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): Params { - 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; } @@ -51,58 +54,6 @@ function meta(params: Params): Params { )(params); } -/** - * coord 配置 - * @param params - */ -function coord(params: Params): Params { - const { chart } = params; - - chart.coordinate().reflect('y'); - - return params; -} - -/** - * axis 配置 - * 词云图不显示轴信息 - * @param params - */ -function axis(params: Params): Params { - const { chart } = params; - - chart.axis(false); - - return params; -} - -/** - * label 配置 - * 词云图不显示 label 信息 - * @param params - */ -function label(params: Params): Params { - const { chart } = params; - const geometry = findGeometry(chart, 'point'); - - geometry.label(false); - - return params; -} - -/** - * legend 配置 - * 词云图不显示 legend 信息 - * @param params - */ -function legend(params: Params): Params { - const { chart } = params; - - chart.legend(false); - - return params; -} - /** * 词云图适配器 * @param chart @@ -110,5 +61,5 @@ function legend(params: Params): Params { */ export function adaptor(params: Params) { // 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); } diff --git a/src/plots/word-cloud/index.ts b/src/plots/word-cloud/index.ts index f828e0db78d..aefcec7ab55 100644 --- a/src/plots/word-cloud/index.ts +++ b/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 @@ -23,7 +23,7 @@ export class WordCloud extends Plot { showTitle: false, showMarkers: false, showCrosshairs: false, - customContent(_, data: { data: DataItem; mappingData: { color: string } }[]) { + customContent(_, data: { data: Tag; mappingData: { color: string } }[]) { if (!data.length) return; // 不完全采用模板字符串,是为了去掉换行符和部分空格, // 便于测试。 diff --git a/src/plots/word-cloud/shapes/word-cloud.ts b/src/plots/word-cloud/shapes/word-cloud.ts index 37538e22b1b..6b5d89ec933 100644 --- a/src/plots/word-cloud/shapes/word-cloud.ts +++ b/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) { diff --git a/src/plots/word-cloud/types.ts b/src/plots/word-cloud/types.ts index dd8426726b2..08a7568017c 100644 --- a/src/plots/word-cloud/types.ts +++ b/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; /** 字体样式 */ @@ -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 */ diff --git a/src/plots/word-cloud/utils.ts b/src/plots/word-cloud/utils.ts index ddd27fd0efb..afc7fbb7c93 100644 --- a/src/plots/word-cloud/utils.ts +++ b/src/plots/word-cloud/utils.ts @@ -1,17 +1,18 @@ 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): DataItem[] { +export function transform(params: Params): 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 []; } @@ -19,8 +20,23 @@ export function transform(params: Params): DataItem[] { 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), diff --git a/src/utils/transform/word-cloud.ts b/src/utils/transform/word-cloud.ts index dd9ea45b6df..8c3644dab79 100644 --- a/src/utils/transform/word-cloud.ts +++ b/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], @@ -39,45 +29,47 @@ const DEFAULT_OPTIONS: Options = { * 根据对应的数据对象,计算每个 * 词语在画布中的渲染位置,并返回 * 计算后的数据对象 + * @param words * @param options */ -export function wordCloud(data: Data, options?: Partial) { +export function wordCloud(words: Word[], options?: Partial): 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({ @@ -95,7 +87,7 @@ export function transform(data: Data, options: Options) { opacity: 0, }); - return tags as DataItem[]; + return tags; } /*