From bb9e01cd37aa27cef66aa9fcab85f9e534160503 Mon Sep 17 00:00:00 2001 From: hustcc Date: Fri, 12 Nov 2021 12:55:16 +0800 Subject: [PATCH] feat(sankey): nodelink data supported (#2967) * fix(funnel): do not mutable data * revert(funnel): clone data firstly * chore: remove unused import * feat(sankey): node link data supported --- __tests__/unit/plots/sankey/node-link-spec.ts | 38 ++++++++++++ src/plots/sankey/helper.ts | 35 ++++++++--- src/plots/sankey/types.ts | 60 ++++++++++++++++--- 3 files changed, 116 insertions(+), 17 deletions(-) create mode 100644 __tests__/unit/plots/sankey/node-link-spec.ts diff --git a/__tests__/unit/plots/sankey/node-link-spec.ts b/__tests__/unit/plots/sankey/node-link-spec.ts new file mode 100644 index 0000000000..5a83664e5d --- /dev/null +++ b/__tests__/unit/plots/sankey/node-link-spec.ts @@ -0,0 +1,38 @@ +import { Sankey } from '../../../../src'; +import { createDiv } from '../../../utils/dom'; + +describe('sankey', () => { + it('dataType = nodeLink', () => { + const ALIPAY_DATA = { + nodes: [ + { id: 'A', name: 'A' }, + { id: 'B', name: 'B' }, + { id: 'C', name: 'C' }, + { id: 'D', name: 'D' }, + { id: 'E', name: 'E' }, + { id: 'F', name: 'F', fixedValue: 10 }, + ], + links: [ + { source: 0, target: 1, value: 160 }, + { source: 1, target: 2, value: 10 }, + { source: 2, target: 3, value: 8 }, + { source: 4, target: 3, value: 27 }, + ], + }; + + const sankey = new Sankey(createDiv(), { + height: 500, + dataType: 'node-link', + data: ALIPAY_DATA, + }); + + sankey.render(); + + const elements = sankey.chart.views[1].geometries[0].elements; + + // F 元素会现实出来 + expect(elements.some((el) => el.getData().name === 'F')).toBe(true); + + sankey.destroy(); + }); +}); diff --git a/src/plots/sankey/helper.ts b/src/plots/sankey/helper.ts index 9a6d48aa4a..bc42a88d2d 100644 --- a/src/plots/sankey/helper.ts +++ b/src/plots/sankey/helper.ts @@ -1,9 +1,19 @@ import { isRealNumber, pick } from '../../utils'; import { transformDataToNodeLinkData } from '../../utils/data'; -import { sankeyLayout } from './layout'; +import { Data } from '../../types'; +import { sankeyLayout, SankeyLayoutInputData } from './layout'; import { cutoffCircle } from './circle'; import { SankeyOptions } from './types'; +/** + * 是否是 node-link 类型的数据结构 + * @param dataTyp + * @returns + */ +function isNodeLink(dataType: string) { + return dataType === 'node-link'; +} + export function getNodeWidthRatio(nodeWidth: number, nodeWidthRatio: number, width: number) { return isRealNumber(nodeWidth) ? nodeWidth / width : nodeWidthRatio; } @@ -20,6 +30,7 @@ export function getNodePaddingRatio(nodePadding: number, nodePaddingRatio: numbe */ export function transformToViewsData(options: SankeyOptions, width: number, height: number) { const { + dataType, data, sourceField, targetField, @@ -34,13 +45,19 @@ export function transformToViewsData(options: SankeyOptions, width: number, heig rawFields = [], } = options; - const sankeyLayoutInputData = transformDataToNodeLinkData( - cutoffCircle(data, sourceField, targetField), - sourceField, - targetField, - weightField, - rawFields - ); + let sankeyLayoutInputData: unknown; + + if (!isNodeLink(dataType)) { + sankeyLayoutInputData = transformDataToNodeLinkData( + cutoffCircle(data as Data, sourceField, targetField), + sourceField, + targetField, + weightField, + rawFields + ); + } else { + sankeyLayoutInputData = data; + } // 3. layout 之后的数据 const { nodes, links } = sankeyLayout( @@ -51,7 +68,7 @@ export function transformToViewsData(options: SankeyOptions, width: number, heig nodeSort, nodeDepth, }, - sankeyLayoutInputData + sankeyLayoutInputData as SankeyLayoutInputData ); // 4. 生成绘图数据 diff --git a/src/plots/sankey/types.ts b/src/plots/sankey/types.ts index 75ad071592..dce5697ec5 100644 --- a/src/plots/sankey/types.ts +++ b/src/plots/sankey/types.ts @@ -1,20 +1,64 @@ import { Data, Options, State, StyleAttr } from '../../types'; import { NodeDepth, NodeSort } from './layout'; +/** + * node-link 数据类型的结构 + */ +export type NodeLinkData = { + /** + * 节点数据 + */ + readonly nodes: { + /** + * id 唯一即可,一般可以直接等于 name + */ + readonly id: string; + /** + * 节点的名称,用于 UI 上的现实 + */ + readonly name: string; + /** + * 节点的值,不传则节点大小有来源求和决定 + */ + readonly fixedValue?: number; + }[]; + /** + * + */ + readonly links: { + /** + * 来源节点在 nodes 中的 index + */ + readonly source: number; + /** + * 目标节点在 nodes 中的 index + */ + readonly target: number; + /** + * 边的值 + */ + readonly value: number; + }[]; +}; + /** 配置类型定义 */ -export interface SankeyOptions extends Omit { +export interface SankeyOptions extends Omit { + /** + * 数据集的类型,默认为 detail + */ + readonly dataType?: 'node-link' | 'detail'; /** - * 来源字段 + * 来源字段,dataType = 'node-link' 的时候,不用传 */ - readonly sourceField: string; + readonly sourceField?: string; /** - * 去向字段 + * 去向字段,dataType = 'node-link' 的时候,不用传 */ - readonly targetField: string; + readonly targetField?: string; /** - * 权重字段 + * 权重字段,dataType = 'node-link' 的时候,不用传 */ - readonly weightField: string; + readonly weightField?: string; /** * 附加的 元字段 */ @@ -22,7 +66,7 @@ export interface SankeyOptions extends Omit